Revise HandlerMappingIntrospector caching
Expose methods to set and reset cache to use from a Filter instead of a method to create such a Filter. Also use cached results only if they match by dispatcher type and requestURI. See gh-31588
This commit is contained in:
@@ -23,7 +23,10 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -42,12 +45,14 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector.CachedResult;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.testfixture.servlet.MockFilterChain;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
@@ -202,68 +207,67 @@ public class HandlerMappingIntrospectorTests {
|
||||
|
||||
@Test
|
||||
void cacheFilter() throws Exception {
|
||||
testCacheFilter(new MockHttpServletRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheFilterRestoresPreviousValues() throws Exception {
|
||||
TestMatchableHandlerMapping previousMapping = new TestMatchableHandlerMapping();
|
||||
CorsConfiguration previousCorsConfig = new CorsConfiguration();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setAttribute(HandlerMappingIntrospector.MAPPING_ATTRIBUTE, previousMapping);
|
||||
request.setAttribute(HandlerMappingIntrospector.CORS_CONFIG_ATTRIBUTE, previousCorsConfig);
|
||||
|
||||
testCacheFilter(request);
|
||||
|
||||
assertThat(previousMapping.getInvocationCount()).isEqualTo(0);
|
||||
assertThat(request.getAttribute(HandlerMappingIntrospector.MAPPING_ATTRIBUTE)).isSameAs(previousMapping);
|
||||
assertThat(request.getAttribute(HandlerMappingIntrospector.CORS_CONFIG_ATTRIBUTE)).isSameAs(previousCorsConfig);
|
||||
}
|
||||
|
||||
private void testCacheFilter(MockHttpServletRequest request) throws IOException, ServletException {
|
||||
CorsConfiguration corsConfig = new CorsConfiguration();
|
||||
TestMatchableHandlerMapping mapping = new TestMatchableHandlerMapping();
|
||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||
context.registerBean(TestMatchableHandlerMapping.class, () -> mapping);
|
||||
context.refresh();
|
||||
mapping.registerHandler("/test", new TestHandler(corsConfig));
|
||||
|
||||
HandlerMappingIntrospector introspector = initIntrospector(context);
|
||||
HandlerMappingIntrospector introspector = initIntrospector(mapping);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
Filter filter = (req, res, chain) -> {
|
||||
try {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
introspector.getMatchableHandlerMapping((HttpServletRequest) req);
|
||||
introspector.getCorsConfiguration((HttpServletRequest) req);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
chain.doFilter(req, res);
|
||||
};
|
||||
MockFilterChain filterChain = new MockFilterChain(
|
||||
new TestServlet(), new CacheResultFilter(introspector), new AuthFilter(introspector, corsConfig));
|
||||
|
||||
HttpServlet servlet = new HttpServlet() {
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse res) {
|
||||
try {
|
||||
res.getWriter().print("Success");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new MockFilterChain(servlet, introspector.createCacheFilter(), filter)
|
||||
.doFilter(request, response);
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
assertThat(response.getContentAsString()).isEqualTo("Success");
|
||||
assertThat(mapping.getInvocationCount()).isEqualTo(1);
|
||||
assertThat(mapping.getMatchCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
private HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
|
||||
@Test
|
||||
void cacheFilterWithNestedDispatch() throws Exception {
|
||||
CorsConfiguration corsConfig1 = new CorsConfiguration();
|
||||
CorsConfiguration corsConfig2 = new CorsConfiguration();
|
||||
|
||||
TestMatchableHandlerMapping mapping1 = new TestMatchableHandlerMapping();
|
||||
TestMatchableHandlerMapping mapping2 = new TestMatchableHandlerMapping();
|
||||
|
||||
mapping1.registerHandler("/1", new TestHandler(corsConfig1));
|
||||
mapping2.registerHandler("/2", new TestHandler(corsConfig2));
|
||||
|
||||
HandlerMappingIntrospector introspector = initIntrospector(mapping1, mapping2);
|
||||
|
||||
MockFilterChain filterChain = new MockFilterChain(
|
||||
new TestServlet(),
|
||||
new CacheResultFilter(introspector),
|
||||
new AuthFilter(introspector, corsConfig1),
|
||||
(req, res, chain) -> chain.doFilter(new MockHttpServletRequest("GET", "/2"), res),
|
||||
new CacheResultFilter(introspector),
|
||||
new AuthFilter(introspector, corsConfig2));
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filterChain.doFilter(new MockHttpServletRequest("GET", "/1"), response);
|
||||
|
||||
assertThat(response.getContentAsString()).isEqualTo("Success");
|
||||
assertThat(mapping1.getInvocationCount()).isEqualTo(2);
|
||||
assertThat(mapping2.getInvocationCount()).isEqualTo(1);
|
||||
assertThat(mapping1.getMatchCount()).isEqualTo(1);
|
||||
assertThat(mapping2.getMatchCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
private HandlerMappingIntrospector initIntrospector(TestMatchableHandlerMapping... mappings) {
|
||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||
int index = 0;
|
||||
for (TestMatchableHandlerMapping mapping : mappings) {
|
||||
context.registerBean("mapping" + index++, TestMatchableHandlerMapping.class, () -> mapping);
|
||||
}
|
||||
context.refresh();
|
||||
return initIntrospector(context);
|
||||
}
|
||||
|
||||
private static HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
|
||||
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
|
||||
introspector.setApplicationContext(context);
|
||||
introspector.afterPropertiesSet();
|
||||
@@ -327,23 +331,111 @@ public class HandlerMappingIntrospectorTests {
|
||||
}
|
||||
|
||||
|
||||
private static class TestMatchableHandlerMapping implements MatchableHandlerMapping {
|
||||
private static class TestMatchableHandlerMapping extends SimpleUrlHandlerMapping {
|
||||
|
||||
private int invocationCount;
|
||||
|
||||
private int matchCount;
|
||||
|
||||
public int getInvocationCount() {
|
||||
return this.invocationCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerExecutionChain getHandler(HttpServletRequest request) {
|
||||
this.invocationCount++;
|
||||
return new HandlerExecutionChain(new Object());
|
||||
public int getMatchCount() {
|
||||
return this.matchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
||||
throw new UnsupportedOperationException();
|
||||
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
|
||||
this.invocationCount++;
|
||||
Object handler = super.getHandlerInternal(request);
|
||||
if (handler != null) {
|
||||
this.matchCount++;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestHandler implements CorsConfigurationSource {
|
||||
|
||||
private final CorsConfiguration corsConfig;
|
||||
|
||||
private TestHandler(CorsConfiguration corsConfig) {
|
||||
this.corsConfig = corsConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
return this.corsConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class CacheResultFilter implements Filter {
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
private CacheResultFilter(HandlerMappingIntrospector introspector) {
|
||||
this.introspector = introspector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws ServletException {
|
||||
|
||||
CachedResult previousValue = this.introspector.setCache((HttpServletRequest) req);
|
||||
try {
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ServletException("HandlerMapping introspection failed", ex);
|
||||
}
|
||||
finally {
|
||||
this.introspector.resetCache(req, previousValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AuthFilter implements Filter {
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
private final CorsConfiguration corsConfig;
|
||||
|
||||
private AuthFilter(HandlerMappingIntrospector introspector, CorsConfiguration corsConfig) {
|
||||
this.introspector = introspector;
|
||||
this.corsConfig = corsConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
try {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) req;
|
||||
assertThat(introspector.getMatchableHandlerMapping(httpRequest)).isNotNull();
|
||||
assertThat(introspector.getCorsConfiguration(httpRequest)).isSameAs(corsConfig);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse res) {
|
||||
try {
|
||||
res.getWriter().print("Success");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user