#1831 - Reset dummy invocation cache on request completion.

This commit is contained in:
Oliver Drotbohm
2022-09-14 16:17:30 +02:00
parent e75afe5603
commit f49758aa00
3 changed files with 63 additions and 0 deletions

View File

@@ -19,6 +19,9 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -26,6 +29,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import org.springframework.hateoas.server.core.DummyInvocationUtils;
import org.springframework.hateoas.server.mvc.RepresentationModelProcessorHandlerMethodReturnValueHandler;
import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker;
import org.springframework.hateoas.server.mvc.UriComponentsContributor;
@@ -33,6 +37,8 @@ import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@@ -93,6 +99,15 @@ class WebMvcHateoasConfiguration {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
hypermediaConverters.augmentServer(converters);
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors(org.springframework.web.servlet.config.annotation.InterceptorRegistry)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(DummyInvocationUtilsCacheClearer.INSTANCE);
}
}
/**
@@ -132,4 +147,25 @@ class WebMvcHateoasConfiguration {
return bean;
}
}
/**
* {@link HandlerInterceptor} to clear the cache in {@link DummyInvocationUtils}.
*
* @author Oliver Drotbohm
*/
enum DummyInvocationUtilsCacheClearer implements HandlerInterceptor {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
DummyInvocationUtils.resetCache();
}
}
}

View File

@@ -180,6 +180,10 @@ public class DummyInvocationUtils {
return new DefaultMethodInvocation(type, method, parameters);
}
public static void resetCache() {
CACHE.remove();
}
@SuppressWarnings("unchecked")
private static <T> T getProxyWithInterceptor(Class<?> type, InvocationRecordingMethodInterceptor interceptor,
ClassLoader classLoader) {

View File

@@ -42,6 +42,7 @@ import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule;
import org.springframework.hateoas.mediatype.uber.Jackson2UberModule;
import org.springframework.hateoas.server.SimpleRepresentationModelAssembler;
import org.springframework.hateoas.server.core.DummyInvocationUtils;
import org.springframework.hateoas.support.Employee;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -51,7 +52,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.ConcurrentLruCache;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -263,6 +266,20 @@ class HypermediaWebMvcConfigurerTest {
.andExpect(status().isIAmATeapot());
}
@Test // #1831
void wipesDummyInvocationsCachePerRequest() throws Exception {
setUp(HalWebMvcConfig.class);
this.mockMvc.perform(get("/dynamicLink"))
.andExpect(status().isOk());
ThreadLocal<ConcurrentLruCache<?, ?>> cache = (ThreadLocal<ConcurrentLruCache<?, ?>>) ReflectionTestUtils.getField(
null, DummyInvocationUtils.class, "CACHE");
assertThat(cache.get().size()).isZero();
}
private void verifyRootUriServesHypermedia(MediaType mediaType) throws Exception {
verifyRootUriServesHypermedia(mediaType, mediaType);
}
@@ -468,6 +485,12 @@ class HypermediaWebMvcConfigurerTest {
return this.assembler.toModel(newEmployee);
}
@GetMapping("/dynamicLink")
EntityModel<Employee> dynamicLink() {
return EntityModel.of(employees.get(0)) //
.add(linkTo(methodOn(TestController.class).employee("1")).withSelfRel());
}
}
static class EmployeeResourceAssembler implements SimpleRepresentationModelAssembler<Employee> {