如何在Spring MVC中将请求映射到HTML文件?

基本配置文件看起来不直观。

如果我创建简单的hello world示例,然后将home.jsp重命名为home.html并编辑servlet-context.xml文件

     

     

我开始出错了

 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' 

为什么? 什么suffix属性意味着什么?

UPDATE

我的控制器如下。 如您所见,它不包含文件扩展名

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

问题的背景

首先要理解的是:它不是渲染jsp文件的spring。 它是JspServlet(org.apache.jasper.servlet.JspServlet)。 这个servlet带有Tomcat(jasper编译器)而不是spring。 这个JspServlet知道如何编译jsp页面以及如何将它作为html文本返回给客户端。 默认情况下,tomcat中的JspServlet只处理匹配两种模式的请求:* .jsp和* .jspx。

现在,当spring使用InternalResourceView (或JstlView )呈现视图时,确实发生了三件事:

  1. 从模型中获取所有模型参数(由控制器处理程序方法返回,即"public ModelAndView doSomething() { return new ModelAndView("home") }"
  2. 将这些模型参数公开为请求属性(以便JspServlet可以读取它)
  3. 转发请求到JspServlet。 RequestDispatcher知道应该将每个* .jsp请求转发到JspServlet(因为这是默认的tomcat配置)

当您只是将视图名称更改为home.html时,tomcat将知道如何处理请求。 这是因为没有servlet处理* .html请求。

怎么解决这个问题。 有三个最明显的解决方案:

  1. 将html暴露为资源文件
  2. 指示JspServlet也处理* .html请求
  3. 编写自己的servlet(或传递给另一个现有的servlet请求* .html)。

初始配置 (仅处理jsp)

首先让我们假设我们配置没有xml文件的spring(仅基于@Configuration批注和spring的WebApplicationInitializer接口)。

基本配置如下

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

在上面的示例中,我使用UrlBasedViewResolver和后备视图类JstlView,但您可以在示例中使用InternalResourceViewResolver并不重要。

上面的示例使用一个视图解析器配置应用程序,该解析器处理以.jsp结尾的jsp文件。 注意:如开头所述,JstlView确实使用tomcat的RequestDispatcher将请求转发给JspSevlet以将jsp编译为html。

解决方案1的实现 – 将html暴露为资源文件:

我们修改WebConfig类以添加匹配的新资源。 我们还需要修改jstlViewResolver,以便它既不带前缀也不带后缀:

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

通过添加这个,我们说每个请求到http://my.server/someurl/resources/的每一个都被映射到您的web目录下的资源目录。 因此,如果您将home.html放在资源目录中并将浏览器指向http://my.server/someurl/resources/home.html ,则会提供该文件。 要通过控制器处理此问题,请返回资源的完整路径:

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

如果你在同一个目录中放置一些jsp文件(不仅是* .html文件),比如在同一资源目录中的home_dynamic.jsp,你可以用类似的方式访问它,但你需要使用服务器上的实际路径。 该路径不以/ someurl /开头,因为这只是以.html结尾的html资源的映射。 在这种情况下,jsp是动态资源,最后由JspServlet使用磁盘上的实际路径访问。 所以访问jsp的正确方法是:

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

要在基于xml的配置中实现此目的,您需要使用:

  

并修改您的jstl视图解析器:

      

解决方案2的实施

在这个选项中,我们使用tomcat的JspServlet来处理静态文件。 因此,你可以在你的html文件中使用jsp标签:)如果你这样做,你当然可以选择它。 很可能你想使用普通的html,所以根本不使用jsp标签,内容将像静态html一样提供。

首先,我们删除视图解析器的前缀和后缀,如上例所示:

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

现在我们添加JspServlet来处理* .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"); } } 

重要的是,要使此类可用,您需要在tomcat的安装中添加jasper.jar,仅用于编译时。 如果你有maven应用程序,那么使用为jar提供的scope =这是非常容易的。 maven中的依赖关系如下所示:

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

如果你想以xml的方式做到这一点。 您需要注册jsp servlet来处理* .html请求,因此您需要在web.xml中添加以下条目

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

现在在您的控制器中,您可以像以前的示例一样访问html和jsp文件。 优点是解决方案1中不需要“/ someurl /”额外映射。您的控制器将如下所示:

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

指向你的jsp,你做的完全一样:

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

解决方案3的实施

第三种解决方案在某种程度上是解决方案1和解决方案2的组合。因此,在这里我们希望将所有请求传递给* .html到其他一些servlet。 你可以编写自己的或者寻找已经存在的servlet的一些好的候选者。

如上所述,我们首先清理视图解析器的前缀和后缀:

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

现在我们编写自己的servlet(或重用一些现有的)来代替使​​用tomcat的JspServlet:

 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(); } } } } } 

我们现在指示spring将所有对* .html的请求传递给我们的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"); } } 

优点(或缺点,取决于你想要的)是jsp标签显然不会被处理。 控制器看起来像往常一样:

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

而对于jsp:

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

Resolver类用于解析视图类的资源,依次查看类,从资源生成视图。 例如,使用典型的InternalResourceViewResolver,如下所示:

     

视图名称“home”将映射为“/WEB-INT/views/home.jsp”,然后使用视图类InternalResourceView(用于JSP)转换为JSP视图。 如果用“.html”替换后缀值,Spring可以获取特定资源“/WEB-INT/views/home.html”,但不知道如何生成它。

普通.html文件是静态的,不需要特殊的ViewResolver。 您应该为html页面设置一个静态文件夹,如下所示。

例如:

  

好吧,似乎你没有设置视图的顺序

例如,如果您的项目具有jsp,json,velocity,freemarker等视图,则可以使用所有这些视图 (可能需要新版本的spring,3.1 +),但只有一个视图将被选择呈现给客户端,这取决于您的视图的顺序,顺序越低,视图越喜欢

例如,你设置jsp视图的顺序是1,而freemarker视图的顺序是2,它们的视图名称都是“home”,spring会选择view.jsp(如果你设置后缀为.jsp)。 好吧,如果您的视图名称是“index”,则没有index.jsp而是index.ftl(假设您将freemarker的视图设置为.ftl),spring将选择后者。

使用spring的java配置示例代码,可以轻松转换为xml风格。

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

请参阅setOrder()方法!

json,jsonp和其他类型的视图可以使用ontentNegotiation,你可以在spring的文档中找到它。

最后, html视图 ,我的意思是, 完全静态文件 ,弹簧默认不支持。 我想静态文件不需要java渲染。 您可以使用以下代码使用静态映射:

  

或者使用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); } 

在你的@RequestMapping方法中,你应该重定向它!

好吧, 如果你不想重定向 ,只需将html视图设置为动态视图(freemark,velecity等),这样就行了!

希望它有用!

Spring MVC不允许您通过控制器呈现静态资源。 正如阿伦所说,它应该通过resources来提供。

如果我错了,请纠正我,但似乎你想要一个index.html作为头版。 要实现这一点,您应该将一个Controller(比如IndexController)映射到/index.html 。 然后,您应该在web.xml配置您的欢迎文件是index.html 。 这样,无论何时指向应用程序的根目录,容器都会查找“/index.html”,然后查找映射到/index.html URL的Controller。

所以,你的控制器应该是这样的:

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

并在您的web.xml中

  index.html  

希望这可以帮助。

我认为InternalResourceViewResolver支持servlet和jsp文件。 根据Spring的API javadocs的后缀是“在构建URL时附加到视图名称”。 它不是文件的扩展名,即使它非常具有误导性。 我检查了UrlBasedViewResolver setSuffix()类。

可能如果他们将其命名为viewSuffix,我想它可能更有意义。

您遇到此问题,因为可能没有为映射* .html注册任何servlet。
因此调用以“默认servlet”结束,这是一个servlet-mapping注册的/可能是你的DispatcherServlet。
现在,Dispatcher servlet找不到一个控制器来处理home.html的请求,因此找到了你看到的消息。
要解决此问题,您可以注册* .html扩展名以由JSPServlet处理,然后它应该干净地工作。