diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 23550a46e04..37dac69b23c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -149,6 +149,7 @@ * @author Chris Beams * @author Rossen Stoyanchev * @author Sebastien Deleuze + * @author Filip Hrisafov * @see org.springframework.web.HttpRequestHandler * @see org.springframework.web.servlet.mvc.Controller * @see org.springframework.web.context.ContextLoaderListener @@ -261,8 +262,8 @@ public class DispatcherServlet extends FrameworkServlet { /** Store default strategy implementations. */ private static @Nullable Properties defaultStrategies; - /** Detect all HandlerMappings or just expect "handlerMapping" bean?. */ - private boolean detectAllHandlerMappings = true; + /** Detect all HandlerMappings, only local HandlerMappings or just expect "handlerMapping" bean?. */ + private DetectionStrategy detectHandlerMappingsStrategy = DetectionStrategy.ALL_BEANS; /** Detect all HandlerAdapters or just expect "handlerAdapter" bean?. */ private boolean detectAllHandlerAdapters = true; @@ -374,9 +375,21 @@ public DispatcherServlet(WebApplicationContext webApplicationContext) { * just a single bean with name "handlerMapping" will be expected. *

Default is "true". Turn this off if you want this servlet to use a single * HandlerMapping, despite multiple HandlerMapping beans being defined in the context. + * @see #detectLocalHandlerMappingsOnly() */ public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) { - this.detectAllHandlerMappings = detectAllHandlerMappings; + this.detectHandlerMappingsStrategy = + (detectAllHandlerMappings ? DetectionStrategy.ALL_BEANS : DetectionStrategy.SINGLE_BEAN); + } + + /** + * Configures the servlet to only detect HandlerMapping beans from the local context. + *

The default is to detect HandlerMappings from all context ancestors. + * @since 7.0 + * @see #setDetectAllHandlerMappings + */ + public void detectLocalHandlerMappingsOnly() { + this.detectHandlerMappingsStrategy = DetectionStrategy.LOCAL_BEANS; } /** @@ -506,25 +519,40 @@ else if (logger.isDebugEnabled()) { private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; - if (this.detectAllHandlerMappings) { - // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. - Map matchingBeans = - BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); - if (!matchingBeans.isEmpty()) { - this.handlerMappings = new ArrayList<>(matchingBeans.values()); - // We keep HandlerMappings in sorted order. - AnnotationAwareOrderComparator.sort(this.handlerMappings); - } - } - else { - try { - HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); - this.handlerMappings = Collections.singletonList(hm); + this.handlerMappings = switch (this.detectHandlerMappingsStrategy) { + case ALL_BEANS -> { + // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. + Map matchingBeans = + BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); + if (!matchingBeans.isEmpty()) { + List handlerMappings = new ArrayList<>(matchingBeans.values()); + // We keep HandlerMappings in sorted order. + AnnotationAwareOrderComparator.sort(handlerMappings); + yield handlerMappings; + } + yield null; + } + case LOCAL_BEANS -> { + Map matchingBeans = context.getBeansOfType(HandlerMapping.class, true, false); + if (!matchingBeans.isEmpty()) { + List handlerMappings = new ArrayList<>(matchingBeans.values()); + // We keep HandlerMappings in sorted order. + AnnotationAwareOrderComparator.sort(handlerMappings); + yield handlerMappings; + } + yield null; } - catch (NoSuchBeanDefinitionException ex) { - // Ignore, we'll add a default HandlerMapping later. + case SINGLE_BEAN -> { + try { + HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); + yield Collections.singletonList(hm); + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore, we'll add a default HandlerMapping later. + yield null; + } } - } + }; // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. @@ -1405,4 +1433,24 @@ private static String getRequestUri(HttpServletRequest request) { return uri; } + /** + * The DetectionStrategy enum represents different strategies for handling + * detection logic of the specific beans. + */ + private enum DetectionStrategy { + /** + * Look for beans in all ancestors of the bean factory. + */ + ALL_BEANS, + /** + * Look for beans only in the configured bean factory. + */ + LOCAL_BEANS, + /** + * Do not look for beans in the configured bean factory. + * Look for the dedicated single bean. + */ + SINGLE_BEAN + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java index f7a9e83b9ad..681812e7703 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java @@ -85,6 +85,7 @@ * @author Juergen Hoeller * @author Rob Harrop * @author Sam Brannen + * @author Filip Hrisafov */ class DispatcherServletTests { @@ -487,6 +488,49 @@ void detectHandlerMappingFromParent() throws ServletException, IOException { assertThat(response.getStatus()).as("Matched through parent controller/handler pair: not response=" + response.getStatus()) .isNotEqualTo(HttpServletResponse.SC_NOT_FOUND); + + request = new MockHttpServletRequest(getServletContext(), "GET", "/unknown.do"); + response = new MockHttpServletResponse(); + complexDispatcherServlet.service(request, response); + assertThat(response.getStatus()).as("Matched through child controller/handler pair: not response=" + response.getStatus()) + .isNotEqualTo(HttpServletResponse.SC_NOT_FOUND); + } + + @Test + void detectHandlerMappingFromChildOnly() throws ServletException, IOException { + // create a parent context that includes a mapping + StaticWebApplicationContext parent = new StaticWebApplicationContext(); + parent.setServletContext(getServletContext()); + parent.registerSingleton("parentHandler", ControllerFromParent.class, new MutablePropertyValues()); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue(new PropertyValue("mappings", URL_KNOWN_ONLY_PARENT + "=parentHandler")); + + parent.registerSingleton("parentMapping", SimpleUrlHandlerMapping.class, pvs); + parent.refresh(); + + DispatcherServlet complexDispatcherServlet = new DispatcherServlet(); + // will have parent + complexDispatcherServlet.setContextClass(ComplexWebApplicationContext.class); + complexDispatcherServlet.setNamespace("test"); + complexDispatcherServlet.detectLocalHandlerMappingsOnly(); + + ServletConfig config = new MockServletConfig(getServletContext(), "complex"); + config.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, parent); + complexDispatcherServlet.init(config); + + MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", URL_KNOWN_ONLY_PARENT); + MockHttpServletResponse response = new MockHttpServletResponse(); + complexDispatcherServlet.service(request, response); + + assertThat(response.getStatus()).as("Matched through parent controller/handler pair: not response=" + response.getStatus()) + .isEqualTo(HttpServletResponse.SC_NOT_FOUND); + + request = new MockHttpServletRequest(getServletContext(), "GET", "/unknown.do"); + response = new MockHttpServletResponse(); + complexDispatcherServlet.service(request, response); + assertThat(response.getStatus()).as("Matched through child controller/handler pair: not response=" + response.getStatus()) + .isNotEqualTo(HttpServletResponse.SC_NOT_FOUND); } @Test