如何在 Spring MVC 中将请求映射到 HTML 文件?
Posted
技术标签:
【中文标题】如何在 Spring MVC 中将请求映射到 HTML 文件?【英文标题】:How to map requests to HTML file in Spring MVC? 【发布时间】:2013-05-12 00:07:17 【问题描述】:基本配置文件看起来不直观。
如果我创建简单的 hello world 示例,然后将 home.jsp
重命名为 home.html
并从以下位置编辑 servlet-context.xml
文件
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
到
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".html" />
</beans:bean>
我开始遇到错误
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
属性是什么意思?
更新
我的控制器如下。如您所见,它不包含文件扩展名
@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";
【问题讨论】:
如果是静态文件则需要使用spring mvc静态资源映射 前<mvc:resources mapping="/WEB-INF/views/home.html" location="/WEB-INF/views/home.html" />
您可以删除InternalResourceViewResolver
,因为不需要处理
@ArunPJohny 没有。不,我只是想尝试文件扩展名;并希望 HTML 传递 jsp 传递的所有管道。
在这种情况下,您需要创建一个控制器并将 URL /WEB-INF/views/home.html
映射到它,它应该返回视图 /WEB-INF/views/home.html
【参考方案1】:
问题背景
首先要了解的是:渲染jsp文件的不是spring。是 JspServlet (org.apache.jasper.servlet.JspServlet) 做的。这个 servlet 带有 Tomcat(jasper 编译器)而不是 spring。这个 JspServlet 知道如何编译 jsp 页面以及如何将其作为 html 文本返回给客户端。 tomcat 中的 JspServlet 默认只处理匹配两种模式的请求:*.jsp 和 *.jspx。
现在,当 spring 使用 InternalResourceView
(或 JstlView
)渲染视图时,真正发生了三件事:
-
从模型中获取所有模型参数(由您的控制器处理程序方法返回,即
"public ModelAndView doSomething() return new ModelAndView("home") "
)
将这些模型参数公开为请求属性(以便 JspServlet 可以读取)
将请求转发到 JspServlet。 RequestDispatcher
知道每个 *.jsp 请求都应该转发给 JspServlet(因为这是默认的 tomcat 配置)
当您简单地将视图名称更改为 home.html 时,tomcat 将不知道如何处理请求。这是因为没有 servlet 处理 *.html 请求。
解决方案
如何解决这个问题。有三个最明显的解决方案:
-
将 html 公开为资源文件
指示 JspServlet 也处理 *.html 请求
编写您自己的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;
在上面的示例中,我使用带有支持视图类 JstlView 的 UrlBasedViewResolver,但您可以使用 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 prefix 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 的配置中实现这一点,您需要使用:
<mvc:resources mapping="/someurl/resources/**" location="/resources/" />
并修改您的 jstl 视图解析器:
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- Please NOTE that it does not matter if you use InternalResourceViewResolver or UrlBasedViewResolver as in annotations example -->
<beans:property name="prefix" value="" />
<beans:property name="suffix" value="" />
</beans:bean>
解决方案 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=provided 非常容易。 maven 中的依赖如下所示:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>$tomcat.libs.version</version>
<scope>provided</scope> <!--- NOTE: scope provided! -->
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>$tomcat.libs.version</version>
<scope>provided</scope>
</dependency>
如果你想以 xml 方式进行。您需要注册 jsp servlet 来处理 *.html 请求,因此您需要将以下条目添加到您的 web.xml
<servlet>
<servlet-name>htmlServlet</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>htmlServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
现在在您的控制器中,您可以像之前的示例一样访问 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;
现在我们不使用 tomcat 的 JspServlet,而是编写自己的 servlet(或重用一些现有的):
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");
【讨论】:
感谢您的广泛回答,但您没有触及web.xml
中的<jsp-config><jsp-property-group><url-pattern>
配置。没有办法在 Spring Boot MVC 中对 existing JSP servlet 执行等效配置吗?在***.com/q/56210281/421049 上查看我的问题。【参考方案2】:
Resolver 类用于解析视图类的资源,视图类依次从资源中生成视图。例如,一个典型的 InternalResourceViewResolver 如下:
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
视图名称“home”将映射为“/WEB-INT/views/home.jsp”,然后使用视图类 InternalResourceView(用于 JSP)转换为 JSP 视图。如果将后缀值替换为“.html”,Spring可以获取具体资源“/WEB-INT/views/home.html”但不知道如何生成。
【讨论】:
@SuzanCioc 你实际上不需要 InternalResourceViewResolver,它是用于 jsp 文件的。您需要的是将您的 html 页面设置为静态资源。【参考方案3】:普通的 .html 文件是静态的,不需要特殊的 ViewResolver。您应该为您的 html 页面设置一个静态文件夹,如 here 所示。
例如:
<mvc:resources mapping="/static/**" location="/static/" />
【讨论】:
【参考方案4】:好吧,看来你没有设置视图的顺序。
例如,如果你的项目有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's java config,可以轻松转换为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 视图,我的意思是,完全静态文件,spring 默认不支持。我想静态文件不需要由 java 渲染。您可以使用以下代码使用静态映射:
<mvc:resources mapping="/static/**" location="/static/" />
或使用 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 等),就可以了!
希望有用!
【讨论】:
【参考方案5】:Spring MVC 不允许您通过控制器呈现静态资源。正如 Arun 所说,它应该通过resources
提供服务。
如果我错了,请纠正我,但您似乎希望将index.html
作为首页。为此,您应该有一个映射到/index.html
的控制器(比如IndexController)。然后,您应该在您的web.xml
中配置您的欢迎文件是index.html
。这样,每当您指向应用程序的根目录时,您的容器都会查找“/index.html”,进而查找映射到/index.html
URL 的控制器。
因此,您的控制器应该如下所示:
@Controller
@RequestMapping("/index.html")
public class MyIndexController
@RequestMapping(method=RequestMethod.GET)
protected String gotoIndex(Model model) throws Exception
return "myLandingPage";
在你的 web.xml 中
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
希望这会有所帮助。
【讨论】:
但是如何将多个url等/、/index、/home映射到一个静态页面呢?【参考方案6】:我认为InternalResourceViewResolver 支持servlet 和jsp 文件。根据 Spring 的 API javadocs 的后缀是“在构建 URL 时附加到视图名称”。它不是文件的扩展名,即使它非常具有误导性。我检查了UrlBasedViewResolver setSuffix() 类。
如果他们将其命名为 viewSuffix,我猜它可能更有意义。
【讨论】:
【参考方案7】:您遇到此问题是因为可能没有为映射 *.html 注册任何 servlet。 所以调用以“默认 servlet”结束,它注册了一个 / 的 servlet 映射,这可能是你的 DispatcherServlet 。 现在 Dispatcher servlet 找不到控制器来处理对 home.html 的请求以及您所看到的消息。 要解决此问题,您可以注册 *.html 扩展名以由 JSPServlet 处理,然后它应该可以干净地工作。
【讨论】:
以上是关于如何在 Spring MVC 中将请求映射到 HTML 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 spring-mvc 中将日志记录添加到 webflux 端点?
Spring 3 MVC - 将带有前缀的请求参数映射到单个 bean
如何在 Spring Boot 中将嵌套的 JSON 对象映射为 SQL 表行