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