问题背景
首先要明白的是:渲染 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;
}
}
在上面的例子中,我使用了基于URL的视图解析器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("");
resolver.setViewClass(JstlView.class);
resolver.setSuffix("");
return resolver;
}
}
通过添加这个,我们表示所有访问
http://my.server/someurl/resources/的请求都映射到您的网站目录下的资源目录。因此,如果你把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");
}
}
如果您将一些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");
}
在基于xml的配置中实现这一点,您需要使用:
<mvc:resources mapping="/someurl/resources/**" location="/resources/" />
并修改您的JSTL视图解析器:
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!
<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();
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());
servlet.setLoadOnStartup(1);
servlet.addMapping("*.html");
}
}
重要的是,在编译时让这个类可用,您需要添加从Tomcat安装中获取的jasper.jar。如果您有Maven应用程序,可以通过使用范围=提供程序轻松实现此操作。在Maven中的依赖项将如下所示:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>${tomcat.libs.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.libs.version}</version>
<scope>provided</scope>
</dependency>
如果您想以xml方式完成此操作,则需要在web.xml中添加以下内容,以注册jsp servlet处理*.html请求。
<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文件。优点是不需要Solution 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或查找一些已经存在的良好候选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();
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");
}
<mvc:resources mapping="/WEB-INF/views/home.html" location="/WEB-INF/views/home.html" />
- Arun P JohnyInternalResourceViewResolver
,因为没有需要处理的内容。 - Arun P Johny