Cómo mapear las solicitudes al archivo HTML en Spring MVC?

Los archivos de configuración básica no parecen intuitivos.

Si creo un ejemplo simple de hello world, y luego cambie el nombre de home.jsp a home.html y edite el archivo servlet-context.xml de

     

a

     

Empiezo a tener un error

 WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/myapp/WEB-INF/views/home.html] in DispatcherServlet with name 'appServlet' 

¿Por qué? ¿Qué propiedad de suffix significa?

ACTUALIZAR

Mi controlador es el siguiente. Como ve, no contiene extensión de archivo

 @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value = "/", method = RequestMethod.GET) public String home(Locale locale, Model model) { logger.info("Welcome home! The client locale is {}.", locale); Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); String formattedDate = dateFormat.format(date); model.addAttribute("serverTime", formattedDate ); return "home"; } } 

Antecedentes del problema

Lo primero que debemos entender es lo siguiente: NO es la spring la que genera los archivos jsp. Es JspServlet (org.apache.jasper.servlet.JspServlet) que lo hace. Este servlet viene con Tomcat (comstackdor jasper) no con la spring. Este JspServlet sabe cómo comstackr la página jsp y cómo devolverla como texto html al cliente. El JspServlet en Tomcat de forma predeterminada solo maneja las solicitudes que coinciden con dos patrones: * .jsp y * .jspx.

Ahora, cuando Spring muestra la vista con InternalResourceView (o JstlView ), realmente ocurren tres cosas:

  1. obtener todos los parámetros del modelo del modelo (devuelto por el método del controlador de su controlador, es decir, "public ModelAndView doSomething() { return new ModelAndView("home") }" )
  2. exponer estos parámetros de modelo como atributos de solicitud (para que JspServlet pueda leerlo)
  3. Reenviar solicitud a JspServlet. RequestDispatcher sabe que cada solicitud * .jsp se debe reenviar a JspServlet (porque esta es la configuración predeterminada de tomcat)

Cuando simplemente cambia el nombre de la vista a home.html, Tomcat no sabrá cómo manejar la solicitud. Esto se debe a que no hay solicitudes de servlet handling * .html.

Solución

Cómo resolver esto Hay tres soluciones más obvias:

  1. exponer el html como un archivo de recursos
  2. instruir al JspServlet para que también maneje las solicitudes * .html
  3. escriba su propio servlet (o pase a otras solicitudes de servlets existentes a * .html).

Configuración inicial (solo manejando jsp)

Primero supongamos que configuramos el resorte sin archivos xml (solo basándonos en la anotación @Configuration y en la interfaz WebApplicationInitializer de Spring).

La configuración básica estaría siguiendo

 public class MyWebApplicationContext extends AnnotationConfigWebApplicationContext { private static final String CONFIG_FILES_LOCATION = "my.application.root.config"; public MyWebApplicationContext() { super(); setConfigLocation(CONFIG_FILES_LOCATION); } } public class AppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext context = new MyWebApplicationContext(); servletContext.addListener(new ContextLoaderListener(context)); addSpringDispatcherServlet(servletContext, context); } private void addSpringDispatcherServlet(ServletContext servletContext, WebApplicationContext context) { ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context)); dispatcher.setLoadOnStartup(2); dispatcher.addMapping("/"); dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true"); } } package my.application.root.config // (...) @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Autowired @Qualifier("jstlViewResolver") private ViewResolver jstlViewResolver; @Bean @DependsOn({ "jstlViewResolver" }) public ViewResolver viewResolver() { return jstlViewResolver; } @Bean(name = "jstlViewResolver") public ViewResolver jstlViewResolver() { UrlBasedViewResolver resolver = new UrlBasedViewResolver(); resolver.setPrefix("/WEB-INF/internal/"); resolver.setViewClass(JstlView.class); resolver.setSuffix(".jsp"); return resolver; } } 

En el ejemplo anterior, estoy usando UrlBasedViewResolver con la clase de vista de respaldo JstlView, pero puedes usar InternalResourceViewResolver porque en tu ejemplo no importa.

El ejemplo anterior configura la aplicación con solo un solucionador de vista que maneja los archivos jsp que terminan con .jsp . NOTA: Como se dijo al principio, JstlView realmente usa RequestDispatcher de Tomcat para reenviar la solicitud a JspSevlet para comstackr jsp a html.

Implementación en la solución 1 : exponer el html como un archivo de recursos:

Modificamos la clase WebConfig para agregar nuevas coincidencias de recursos. También tenemos que modificar el jstlViewResolver para que no tenga prefijo ni sufijo:

 @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Autowired @Qualifier("jstlViewResolver") private ViewResolver jstlViewResolver; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/someurl/resources/**").addResourceLocations("/resources/"); } @Bean @DependsOn({ "jstlViewResolver" }) public ViewResolver viewResolver() { return jstlViewResolver; } @Bean(name = "jstlViewResolver") public ViewResolver jstlViewResolver() { UrlBasedViewResolver resolver = new UrlBasedViewResolver(); resolver.setPrefix(""); // NOTE: no preffix here resolver.setViewClass(JstlView.class); resolver.setSuffix(""); // NOTE: no suffix here return resolver; } // NOTE: you can use InternalResourceViewResolver it does not matter // @Bean(name = "internalResolver") // public ViewResolver internalViewResolver() { // InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // resolver.setPrefix(""); // resolver.setSuffix(""); // return resolver; // } } 

Al agregar esto decimos que cada una de las solicitudes que van a http: //my.server/someurl/resources/ está asignada al directorio de recursos en su directorio web. Entonces, si coloca su home.html en el directorio de recursos y apunta su navegador a http: //my.server/someurl/resources/home.html, el archivo será servido. Para manejar esto con su controlador, devuelve la ruta completa al recurso:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/someurl/resources/home.html"); // NOTE here there is /someurl/resources } } 

Si coloca en el mismo directorio algunos archivos jsp (no solo * .html), digite home_dynamic.jsp en el mismo directorio de recursos, puede acceder de manera similar, pero necesita usar la ruta real en el servidor. La ruta no comienza con / someurl / porque esta es la asignación solo para los recursos html que terminan en .html). En este contexto, jsp es un recurso dynamic al que accede JspServlet utilizando la ruta real del disco. Entonces la forma correcta de acceder al jsp es:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/resources/home_dynamic.jsp"); // NOTE here there is /resources (there is no /someurl/ because "someurl" is only for static resources } 

Para lograr esto en una configuración basada en xml necesitas usar:

  

y modifique su resolución de vista jstl:

      

Implementación en la solución 2 :

En esta opción, usamos JspServlet de tomcat para manejar también archivos estáticos. Como consecuencia, puede usar tags jsp en sus archivos html 🙂 Por supuesto, es su elección si lo hace o no. Lo más probable es que desee utilizar HTML simple, así que simplemente no use tags jsp y el contenido se servirá como si fuera html estático.

Primero borramos preffix y sufijo para ver resolver como en el ejemplo anterior:

 @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Autowired @Qualifier("jstlViewResolver") private ViewResolver jstlViewResolver; @Bean @DependsOn({ "jstlViewResolver" }) public ViewResolver viewResolver() { return jstlViewResolver; } @Bean(name = "jstlViewResolver") public ViewResolver jstlViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // NOTE: this time I'm using InternalResourceViewResolver and again it does not matter :) resolver.setPrefix(""); resolver.setSuffix(""); return resolver; } } 

Ahora agregamos JspServlet para manejar también archivos * .html:

 public class AppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext context = new MyWebApplicationContext(); servletContext.addListener(new ContextLoaderListener(context)); addStaticHtmlFilesHandlingServlet(servletContext); addSpringDispatcherServlet(servletContext, context); } // (...) private void addStaticHtmlFilesHandlingServlet(ServletContext servletContext) { ServletRegistration.Dynamic servlet = servletContext.addServlet("HtmlsServlet", new JspServlet()); // org.apache.jasper.servlet.JspServlet servlet.setLoadOnStartup(1); servlet.addMapping("*.html"); } } 

Importante es que para hacer que esta clase esté disponible, debe agregar jasper.jar desde la instalación de su tomcat solo para el tiempo de comstackción. Si tiene la aplicación Maven, esto es realmente fácil usando el scope = provisto para el jar. La dependencia en maven se verá así:

  org.apache.tomcat tomcat-jasper ${tomcat.libs.version} provided    org.apache.tomcat tomcat-jsp-api ${tomcat.libs.version} provided  

Si quieres hacerlo de manera xml Debería registrar el servlet jsp para manejar las solicitudes * .html, por lo que debe agregar la siguiente entrada a su web.xml

  htmlServlet org.apache.jasper.servlet.JspServlet 3   htmlServlet *.html  

Ahora en su controlador puede acceder a los archivos html y jsp como en el ejemplo anterior. La ventaja es que no hay ningún mapeo adicional “/ someurl /” que se necesitó en la Solución 1. Su controlador se verá así:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/resources/home.html"); } 

Para apuntar a su jsp, está haciendo exactamente lo mismo:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/resources/home_dynamic.jsp"); } 

Implementación en la solución 3 :

La tercera solución es, de alguna manera, una combinación de solución 1 y solución 2. Por lo tanto, aquí queremos pasar todas las solicitudes a * .html a algún otro servlet. Puede escribir uno propio o buscar algún buen candidato de servlet ya existente.

Como arriba, limpiamos el prefijo y el sufijo para el resolvedor de vista:

 @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Autowired @Qualifier("jstlViewResolver") private ViewResolver jstlViewResolver; @Bean @DependsOn({ "jstlViewResolver" }) public ViewResolver viewResolver() { return jstlViewResolver; } @Bean(name = "jstlViewResolver") public ViewResolver jstlViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // NOTE: this time I'm using InternalResourceViewResolver and again it does not matter :) resolver.setPrefix(""); resolver.setSuffix(""); return resolver; } } 

Ahora, en lugar de usar el JspServlet del tomcat, escribimos nuestro propio servlet (o reutilizamos algunos existentes):

 public class StaticFilesServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); String resourcePath = request.getRequestURI(); if (resourcePath != null) { FileReader reader = null; try { URL fileResourceUrl = request.getServletContext().getResource(resourcePath); String filePath = fileResourceUrl.getPath(); if (!new File(filePath).exists()) { throw new IllegalArgumentException("Resource can not be found: " + filePath); } reader = new FileReader(filePath); int c = 0; while (c != -1) { c = reader.read(); if (c != -1) { response.getWriter().write(c); } } } finally { if (reader != null) { reader.close(); } } } } } 

Ahora instruimos a la spring para pasar todas las solicitudes a * .html a nuestro servlet

 public class AppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext context = new MyWebApplicationContext(); servletContext.addListener(new ContextLoaderListener(context)); addStaticHtmlFilesHandlingServlet(servletContext); addSpringDispatcherServlet(servletContext, context); } // (...) private void addStaticHtmlFilesHandlingServlet(ServletContext servletContext) { ServletRegistration.Dynamic servlet = servletContext.addServlet("HtmlsServlet", new StaticFilesServlet()); servlet.setLoadOnStartup(1); servlet.addMapping("*.html"); } } 

La ventaja (o desventaja, depende de lo que quieras) es que las tags jsp obviamente no serán procesadas. Tu controlador se ve como de costumbre:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/resources/home.html"); } 

Y para jsp:

 @Controller public class HomeController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView home(Locale locale, Model model) { // (...) return new ModelAndView("/resources/home_dynamic.jsp"); } 

La clase Resolver se usa para resolver recursos de una clase de vista, ver clase por turno, genera las vistas desde los recursos. Por ejemplo, con un InternalResourceViewResolver típico como a continuación:

     

Un nombre de vista “home” se mapeará como “/WEB-INT/views/home.jsp” y luego se traducirá a una vista JSP utilizando la clase de vista InternalResourceView (que es para JSP). Si reemplaza el valor del sufijo con “.html”, Spring puede obtener el recurso específico “/WEB-INT/views/home.html” pero no sabe cómo generarlo.

Los archivos .html simples son estáticos y no necesitan un ViewResolver especial. Debe configurar una carpeta estática para sus páginas html como se muestra aquí .

Por ejemplo:

  

Bueno, parece que no estableciste el orden de la vista .

por ejemplo, si su proyecto tiene vistas como jsp, json, velocity, freemarker, etc. puede usar todas ellas (tal vez necesite una nueva versión de spring, 3.1+), pero solo una vista será seleccionada para renderizar al cliente, eso depende del orden de su vista, cuanto más bajo sea el orden, más preferirá la vista .

por ejemplo, establece que el orden de la vista de jsp es 1, y el orden de la vista de freemarker es 2, su nombre de vista es “de inicio”, el resorte elegirá view.jsp (si establece el sufijo a .jsp). Bueno, si el nombre de su vista es “index”, no index.jsp pero index.ftl (supongamos que establece la vista de freemarker en .ftl), spring elegirá el más tarde.

el código de muestra que usa la configuración java de Spring, puede convertirlo fácilmente a xml-style.

 @Bean public InternalResourceViewResolver jspViewResolver() { InternalResourceViewResolver jsp = new InternalResourceViewResolver(); jsp.setOrder(4); jsp.setCache(true); jsp.setViewClass(org.springframework.web.servlet.view.JstlView.class); jsp.setPrefix("/WEB-INF/jsp/"); jsp.setSuffix(".jsp"); return jsp; } @Bean public FreeMarkerViewResolver freeMarkerViewResolver() { FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver(); viewResolver.setCache(true); viewResolver.setPrefix(""); viewResolver.setSuffix(".ftl"); viewResolver.setContentType(ViewConstants.MEDIA_TYPE_HTML); viewResolver.setRequestContextAttribute("request"); viewResolver.setExposeSpringMacroHelpers(true); viewResolver.setExposeRequestAttributes(true); viewResolver.setExposeSessionAttributes(true); viewResolver.setOrder(2); return viewResolver; } 

por favor mira el método setOrder ()

json, jsonp y otro tipo de vistas pueden usar ontentNegotiation, y puedes encontrarlo en los documentos de spring.

finalmente, la vista html , quiero decir, archivos totalmente estáticos , que no son compatibles con los valores predeterminados de la spring. Supongo que el archivo estático no necesita ser renderizado por java. puede usar el mapeo estático usando el siguiente código:

  

o usa la configuración de java:

 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { int cachePeriod = 3600 * 24 * 15; registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCachePeriod(cachePeriod); registry.addResourceHandler("/favicon.ico").addResourceLocations("/").setCachePeriod(cachePeriod); registry.addResourceHandler("/robots.txt").addResourceLocations("/").setCachePeriod(cachePeriod); } 

y en su método @RequestMapping, ¡debe redirigirlo !

Bueno, si no quieres una redirección , simplemente establece la vista html en una vista dinámica (marca de acceso, velecidad, etc.), ¡lo cual estará bien!

espero que sea útil!

Spring MVC no le permite representar recursos estáticos sobre controladores. Como dijo Arun, debería ser servido a través de resources .

Corrígeme si me equivoco, pero parece que quieres un index.html como portada. Para lograr esto, debe tener un Controlador (digamos IndexController) asignado a /index.html . Luego, debe configurar en su web.xml su yo dice que su archivo de bienvenida es index.html . De esta forma, cada vez que señale la raíz de su aplicación, su contenedor buscará un “/index.html” y, a su vez, buscará el controlador asignado a la /index.html URL.

Por lo tanto, su controlador debería verse más o menos así:

 @Controller @RequestMapping("/index.html") public class MyIndexController { @RequestMapping(method=RequestMethod.GET) protected String gotoIndex(Model model) throws Exception { return "myLandingPage"; } } 

Y en tu web.xml

  index.html  

Espero que esto ayude.

Creo que InternalResourceViewResolver admite servlets y archivos jsp. El sufijo según los API javadocs de Spring es el que “se agrega a los nombres cuando se crea una URL”. No es la extensión del archivo, aunque es muy engañoso. Comprobé en la clase UrlBasedViewResolver setSuffix () .

Probablemente si lo nombran como viewSuffix, podría tener más sentido, supongo.

Tiene este problema porque es posible que no haya ningún servlet registrado para asignar * .html.
por lo que la llamada termina con el “servlet predeterminado”, que está registrado con un servlet-mapping de / que probablemente sea su DispatcherServlet.
Ahora el servlet Dispatcher no encuentra un controlador para manejar la solicitud de home.html y, por lo tanto, el mensaje que está viendo.
Para resolver este problema, puede registrar la extensión * .html para que sea manejada por JSPServlet y luego debería funcionar limpiamente.