¿Por qué el MVC de primavera responde con un 404 e informa "No se ha encontrado un mapeo para la petición HTTP con URI [...]en DispatcherServlet"

java spring spring-mvc servlets


Estoy escribiendo una aplicación Spring MVC implementada en Tomcat. Vea el siguiente ejemplo mínimo, completo y verificable

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[] { "/*" };
    }
}

Donde está SpringServletConfig

@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;
    }
}

Finalmente, tengo un @Controller en el paquete com.example.controllers

@Controller
public class ExampleController {
    @RequestMapping(path = "/home", method = RequestMethod.GET)
    public String example() {
        return "index";
    }
}

El nombre de contexto de mi aplicación es Example . Cuando envío una solicitud a

http://localhost:8080/Example/home

la aplicación responde con un HTTP Status 404 y registra lo siguiente

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'

Tengo un recurso JSP en /WEB-INF/jsps/index.jsp Esperaba que Spring MVC usara mi controlador para manejar la solicitud y enviarla al JSP, entonces ¿por qué responde con un 404?


Esto está destinado a ser una publicación canónica para preguntas sobre este mensaje de advertencia.




Answer 1 Sotirios Delimanolis


Su aplicación Spring MVC estándar atenderá todas las solicitudes a través de un DispatcherServlet que haya registrado con su contenedor de Servlet.

Los DispatcherServlet mira a su ApplicationContext y, en su caso, el ApplicationContext registradas con un ContextLoaderListener de granos especiales que necesita para configurar su solicitud lógica de servir. Estos beans se describen en la documentación .

Posiblemente el más importante, frijoles de tipo HandlerMapping map

solicitudes entrantes a manejadores y una lista de preprocesadores y postprocesadores (interceptores de manejador) basados ​​en algunos criterios cuyos detalles varían según la implementación de HandlerMapping . La implementación más popular admite controladores anotados, pero también existen otras implementaciones.

El javadoc de HandlerMapping describe además cómo deben comportarse las implementaciones.

El DispatcherServlet encuentra todos los granos de este tipo y los registra en algún orden (se puede personalizar). Mientras se atiende una solicitud, DispatcherServlet recorre estos objetos HandlerMapping y los prueba con getHandler para encontrar uno que pueda manejar la solicitud entrante, representada como la HttpServletRequest estándar . A partir de 4.3.x, si no encuentra ninguno , registra la advertencia que ve

No se encontró ninguna asignación para la solicitud HTTP con URI [/some/path] en DispatcherServlet con el nombre SomeName

y ya sea lanza un NoHandlerFoundException o inmediatamente comete la respuesta con un código de estado 404 no encontrado.

¿Por qué el DispatcherServlet no encontró un HandlerMapping que pudiera manejar mi solicitud?

La implementación más común de HandlerMapping es RequestMappingHandlerMapping , que maneja el registro de beans @Controller como manejadores (realmente sus métodos anotados @RequestMapping ). Puede declarar un bean de este tipo usted mismo (con @Bean o <bean> u otro mecanismo) o puede usar las opciones integradas . Estos son:

  1. @Configuration su clase @EnableWebMvc con @EnableWebMvc .
  2. Declare un miembro <mvc:annotation-driven /> en su configuración XML.

Como se describe en el enlace anterior, ambos registrarán un bean RequestMappingHandlerMapping (y un montón de otras cosas). Sin embargo, un HandlerMapping no es muy útil sin un controlador. RequestMappingHandlerMapping espera algunos beans @Controller , por lo que debe declararlos también, a través de los métodos @Bean en una configuración Java o declaraciones <bean> en una configuración XML o mediante el escaneo de componentes de las clases anotadas @Controller en cualquiera de los dos. Asegúrese de que estos frijoles estén presentes.

Si recibe el mensaje de advertencia y un 404 y ha configurado todo lo anterior correctamente, está enviando su solicitud al URI incorrecto , uno que no es manejado por un método de controlador anotado @RequestMapping detectado .

La biblioteca spring-webmvc ofrece otras implementaciones de HandlerMapping incorporadas . Por ejemplo, BeanNameUrlHandlerMapping maps

desde URLs hasta frijoles con nombres que empiezan con una barra ("")

y siempre puedes escribir el tuyo. Obviamente, tendrá que asegurarse de que la solicitud que está enviando coincide con al menos uno de los HandlerMapping objeto HandlerMapping registrado .

Si no registra implícita o explícitamente ningún HandlerMapping (o si detectAllHandlerMappings es true ), DispatcherServlet registra algunos valores predeterminados . Estos se definen en DispatcherServlet.properties en el mismo paquete que la clase DispatcherServlet . Son BeanNameUrlHandlerMapping y DefaultAnnotationHandlerMapping (que es similar a RequestMappingHandlerMapping pero en desuso).

Debugging

Spring MVC registrará los controladores registrados a través de RequestMappingHandlerMapping . Por ejemplo, un @Controller como

@Controller
public class ExampleController {
    @RequestMapping(path = "/example", method = RequestMethod.GET, headers = "X-Custom")
    public String example() {
        return "example-view-name";
    }
}

registrará lo siguiente en el nivel INFO

Mapped "{[/example],methods=[GET],headers=[X-Custom]}" onto public java.lang.String com.spring.servlet.ExampleController.example()

Esto describe el mapeo registrado. Cuando vea la advertencia de que no se encontró ningún controlador, compare el URI en el mensaje con la asignación que se muestra aquí. Todas las restricciones especificadas en @RequestMapping deben coincidir para que Spring MVC seleccione el controlador.

Otras implementaciones de HandlerMapping registran sus propias declaraciones que deberían insinuar sus asignaciones y sus controladores correspondientes.

Del mismo modo, habilite el registro de Spring en el nivel DEBUG para ver qué beans registra Spring. Debe informar qué clases anotadas encuentra, qué paquetes analiza y qué beans inicializa. Si los que esperaba no están presentes, revise la configuración de ApplicationContext .

Otros errores comunes

Un DispatcherServlet es solo un Servlet Java EE típico . Usted lo registra con su declaración <web.xml> <servlet-class> y <servlet-mapping> típica , o directamente a través de ServletContext#addServlet en un WebApplicationInitializer , o con cualquier mecanismo que utilice Spring boot. Como tal, debe confiar en la lógica de mapeo de url especificada en la especificación de Servlet , consulte el Capítulo 12. Consulte también

Con eso en mente, un error común es registrar el DispatcherServlet con una asignación de URL de /* , devolviendo un nombre de vista desde un método de controlador @RequestMapping y esperando que se represente un JSP. Por ejemplo, considere un método de controlador como

@RequestMapping(path = "/example", method = RequestMethod.GET)
public String example() {
    return "example-view-name";
}

con un InternalResourceViewResolver

@Bean
public InternalResourceViewResolver resolver() {
    InternalResourceViewResolver vr = new InternalResourceViewResolver();
    vr.setPrefix("/WEB-INF/jsps/");
    vr.setSuffix(".jsp");
    return vr;
}

puede esperar que la solicitud se reenvíe a un recurso JSP en la ruta /WEB-INF/jsps/example-view-name.jsp . Esto no va a suceder. En cambio, suponiendo un nombre de contexto de Example , DisaptcherServlet informará

No se encontró ninguna asignación para la solicitud HTTP con URI [/Example/WEB-INF/jsps/example-view-name.jsp] en DispatcherServlet con el nombre 'dispatcher'

Debido a que DispatcherServlet se asigna a /* y /* coincide con todo (excepto las coincidencias exactas, que tienen mayor prioridad), DispatcherServlet se elegiría para manejar el forward desde JstlView (devuelto por InternalResourceViewResolver ). En casi todos los casos, DispatcherServlet no se configurará para manejar dicha solicitud .

En cambio, en este caso simplista, debe registrar el DispatcherServlet en / , marcándolo como el servlet predeterminado. El servlet predeterminado es la última coincidencia para una solicitud. Esto permitirá que su contenedor de servlet típico elija una implementación de Servlet interna, asignada a *.jsp , para manejar el recurso JSP (por ejemplo, Tomcat tiene JspServlet ), antes de intentar con el servlet predeterminado.

Eso es lo que ves en tu ejemplo.




Answer 2 RoutesMaps.com


Resolví mi problema cuando además de lo descrito anteriormente:`

@Bean
public InternalResourceViewResolver resolver() {
    InternalResourceViewResolver vr = new InternalResourceViewResolver();
    vr.setPrefix("/WEB-INF/jsps/");
    vr.setSuffix(".jsp");
    return vr;
}

added tomcat-embed-jasper:

<dependency>
       <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
       <scope>provided</scope>
</dependency>

`from: el archivo JSP no se procesa en la aplicación web Spring Boot




Answer 3 Acapulco


En mi caso, estaba siguiendo la documentación de Interceptors Spring para la versión 5.1.2 (mientras usaba Spring Boot v2.0.4.RELEASE ) y la clase WebConfig tenía la anotación @EnableWebMvc , que parecía estar en conflicto con algo más en mi aplicación que era evitar que mis activos estáticos se resuelvan correctamente (es decir, no se devolvieron archivos CSS o JS al cliente).

Después de probar un montón de cosas diferentes, he intentado eliminar la @EnableWebMvc y funcionó!

Editar: Aquí está la documentación de referencia que dice que debe eliminar la anotación @EnableWebMvc

Aparentemente, en mi caso, al menos, ya estoy configurando mi aplicación Spring (aunque no usando web.xml o cualquier otro archivo estático, definitivamente es programáticamente), por lo que fue un conflicto allí.




Answer 4 Anil N.P


Me encontré con otra razón para el mismo error.Esto también podría deberse a los archivos de clase no generados para su archivo controller.java.Como resultado de ello,el servlet de despacho mencionado en web.xml no puede asignarlo al método apropiado en la clase controller.

@Controller
Class Controller{
@RequestMapping(value="/abc.html")//abc is the requesting page
public void method()
{.....}
}

En eclipse en Proyecto-> seleccione limpiar -> Proyecto de compilación. Compruebe si el archivo de clase se ha generado para el archivo del controlador en compilaciones en su espacio de trabajo.




Answer 5 Roy


En mi caso,descubrí que mis clases de destino se generaban en un patrón de carpetas que no era el mismo que el de la fuente.Es posible que en el eclipse añada carpetas para contener mis controladores y no las añada como paquetes.Así que terminé definiendo una ruta incorrecta en la configuración de primavera.

Mi clase objetivo era generar clases en la aplicación y me refería a com.happy.app

<context:annotation-config />
<context:component-scan
    base-package="com.happy.app"></context:component-scan> 

Añadí paquetes (no carpetas)para com.happy.app y moví los archivos de las carpetas a los paquetes en el eclipse y eso resolvió el problema.