为什么 Spring MVC 以 404 响应并报告“No mapping found for HTTP request with URI [...] in DispatcherServlet"?
I'm writing a Spring MVC application deployed on Tomcat. See the following minimal, complete, and verifiable example
public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { };
}
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { SpringServletConfig.class };
}
protected String[] getServletMappings() {
return new String[] { "/*" };
}
}
Where SpringServletConfig
is
@Configuration
@ComponentScan("com.example.controllers")
@EnableWebMvc
public class SpringServletConfig {
@Bean
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("/WEB-INF/jsps/");
vr.setSuffix(".jsp");
return vr;
}
}
Finally, I have a @Controller
in the package com.example.controllers
@Controller
public class ExampleController {
@RequestMapping(path = "/home", method = RequestMethod.GET)
public String example() {
return "index";
}
}
My application's context name is Example
. When I send a request to
http://localhost:8080/Example/home
the application responds with an HTTP Status 404 and logs the following
WARN o.s.web.servlet.PageNotFound - No mapping found for HTTP request with URI `[/Example/WEB-INF/jsps/index.jsp]` in `DispatcherServlet` with name 'dispatcher'
I have a JSP resource at /WEB-INF/jsps/index.jsp
I expected Spring MVC to use my controller to handle the request and forward to the JSP, so why is it responding with a 404?
This is meant to be a canonical post for questions about this warning message.
解决方案Your standard Spring MVC application will serve all requests through a DispatcherServlet
that you've registered with your Servlet container.
The DispatcherServlet
looks at its ApplicationContext
and, if available, the ApplicationContext
registered with a ContextLoaderListener
for special beans it needs to setup its request serving logic. These beans are described in the documentation.
Arguably the most important, beans of type HandlerMapping
map
incoming requests to handlers and a list of pre- and post-processors (handler interceptors) based on some criteria the details of which vary by
HandlerMapping
implementation. The most popular implementation supports annotated controllers but other implementations exists as well.
The javadoc of HandlerMapping
further describes how implementations must behave.
The DispatcherServlet
finds all beans of this type and registers them in some order (can be customized). While serving a request, the DispatcherServlet
loops through these HandlerMapping
objects and tests each of them with getHandler
to find one that can handle the incoming request, represented as the standard HttpServletRequest
. As of 4.3.x, if it doesn't find any, it logs the warning that you see
No mapping found for HTTP request with URI
[/some/path]
inDispatcherServlet
with name SomeName
and either throws a NoHandlerFoundException
or immediately commits the response with a 404 Not Found status code.
Why didn't the DispatcherServlet
find a HandlerMapping
that could handle my request?
The most common HandlerMapping
implementation is RequestMappingHandlerMapping
, which handles registering @Controller
beans as handlers (really their @RequestMapping
annotated methods). You can either declare a bean of this type yourself (with @Bean
or <bean>
or other mechanism) or you can use the built-in options. These are:
- Annotate your
@Configuration
class with@EnableWebMvc
. - Declare a
<mvc:annotation-driven />
member in your XML configuration.
As the link above describes, both of these will register a RequestMappingHandlerMapping
bean (and a bunch of other stuff). However, a HandlerMapping
isn't very useful without a handler. RequestMappingHandlerMapping
expects some @Controller
beans so you need to declare those too, through @Bean
methods in a Java configuration or <bean>
declarations in an XML configuration or through component scanning of @Controller
annotated classes in either. Make sure these beans are present.
If you're getting the warning message and a 404 and you've configured all of the above correctly, then you're sending your request to the wrong URI, one that isn't handled by a detected @RequestMapping
annotated handler method.
The spring-webmvc
library offers other built-in HandlerMapping
implementations. For example, BeanNameUrlHandlerMapping
maps
from URLs to beans with names that start with a slash ("/")
and you can always write your own. Obviously, you'll have to make sure the request you're sending matches at least one of the registered HandlerMapping
object's handlers.
If you don't implicitly or explicitly register any HandlerMapping
beans (or if detectAllHandlerMappings
is true
), the DispatcherServlet
registers some defaults. These are defined in DispatcherServlet.properties
in the same package as the DispatcherServlet
class. They are BeanNameUrlHandlerMapping
and DefaultAnnotationHandlerMapping
(which is similar to RequestMappingHandlerMapping
but deprecated).
Debugging
Spring MVC will log handlers registered through RequestMappingHandlerMapping
. For example, a @Controller
like
@Controller
public class ExampleController {
@RequestMapping(path = "/example", method = RequestMethod.GET, headers = "X-Custom")
public String example() {
return "example-view-name";
}
}
will log the following at INFO level
Mapped "{[/example],methods=[GET],headers=[X-Custom]}" onto public java.lang.String com.spring.servlet.ExampleController.example()
This describes the mapping registered. When you see the warning that no handler was found, compare the URI in the message to the mapping listed here. All the restrictions specified in the @RequestMapping
must match for Spring MVC to select the handler.
Other HandlerMapping
implementations log their own statements that should hint to their mappings and their corresponding handlers.
Similarly, enable Spring logging at DEBUG level to see which beans Spring registers. It should report which annotated classes it finds, which packages it scans, and which beans it initializes. If the ones you expected aren't present, then review your ApplicationContext
configuration.
Other common mistakes
A DispatcherServlet
is just a typical Java EE Servlet
. You register it with your typical <web.xml>
<servlet-class>
and <servlet-mapping>
declaration, or directly through ServletContext#addServlet
in a WebApplicationInitializer
, or with whatever mechanism Spring boot uses. As such, you must rely on the url mapping logic specified in the Servlet specification, see Chapter 12. See also
- How are Servlet url mappings in web.xml used?
With that in mind, a common mistake is to register the DispatcherServlet
with a url mapping of /*
, returning a view name from a @RequestMapping
handler method, and expecting a JSP to be rendered. For example, consider a handler method like
@RequestMapping(path = "/example", method = RequestMethod.GET)
public String example() {
return "example-view-name";
}
with an InternalResourceViewResolver
@Bean
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("/WEB-INF/jsps/");
vr.setSuffix(".jsp");
return vr;
}
you might expect the request to be forwarded to a JSP resource at the path /WEB-INF/jsps/example-view-name.jsp
. This won't happen. Instead, assuming a context name of Example
, the DisaptcherServlet
will report
No mapping found for HTTP request with URI
[/Example/WEB-INF/jsps/example-view-name.jsp]
inDispatcherServlet
with name 'dispatcher'
Because the DispatcherServlet
is mapped to /*
and /*
matches everything (except exact matches, which have higher priority), the DispatcherServlet
would be chosen to handle the forward
from the JstlView
(returned by the InternalResourceViewResolver
). In almost every case, the DispatcherServlet
will not be configured to handle such a request.
Instead, in this simplistic case, you should register the DispatcherServlet
to /
, marking it as the default servlet. The default servlet is the last match for a request. This will allow your typical servlet container to chose an internal Servlet implementation, mapped to *.jsp
, to handle the JSP resource (for example, Tomcat has JspServlet
), before trying with the default servlet.
That's what you're seeing in your example.
相关文章