很久没有玩spring mvc了。这次一玩,出问题了。
我发现对于静态页面(*.html),dispatcher servlet处理不了,会报No mapping for GET/url的错误。
我先把环境说一下:
AppInitializer.java
配置dispatcher servlet:
package com.ocean.config;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.register(AppConfig.class);
webApplicationContext.refresh();
DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
AppConfig.java
spring的配置类:
package com.ocean.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@ComponentScan(basePackages = "com.ocean")
public class AppConfig {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/");
internalResourceViewResolver.setSuffix(".html");
return internalResourceViewResolver;
}
}
我想得到静态页面的视图,所以后缀配了.html。
DummyController.java
一个controller:
package com.ocean.controller;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@GetMapping("/home")
public String home(){
return "home";
}
}
当我发出http://localhost:8088/spring_source_war/dummy/home
的请求时,报了404的错误,控制台的打印是:
No mapping for GET /spring_source_war/WEB-INF/home.html
home.html我当然写了。为什么会找不到呢?
那我换成jsp怎么样:
internalResourceViewResolver.setSuffix(".jsp");
这时就能访问WEB-INF下的home.jsp了。
dispatcher serlvet从controller那里拿到了modelAndView,然后它要借助ViewResolver来向用户返回视图。但为什么只有jsp可以?
你会觉得jsp是要从controller那里拿数据返回的视图,而html页面是直接可以访问的,那我就跳过controller直接访问静态页面:
package com.ocean.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.ocean.controller")
public class AppConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/");
internalResourceViewResolver.setSuffix(".html");
return internalResourceViewResolver;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/straight_home").setViewName("/home");
}
}
在AppInitializer中加上webApplicationContext.setServletContext(servletContext);
(在调用refresh方法前加,不然tomcat启动的时候会报找不到servletCxt的错误)。
请求:http://localhost:8088/spring_source_war/straight_home
依旧是No mapping for GET /spring_source_war/WEB-INF/home.html
我们翻翻tomcat的conf目录下的web.xml
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
tomcat自己有专门处理jsp页面的servlet。
要不我们加一个*.html的url-pattern吧。
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
竟然成功了。
不过人家的serlvet是处理jsp的,我们这么做不太好。那应该有专门处理静态页面的servlet吧。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
这个DefaultServlet
是处理静态页面的,我们把它映射的url改为*.html。
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
这么做也是可以的。
不过改之前,它的url-pattern是/,不是拦截所有的请求吗,*.html不是它的子集吗?
因为还有一个老大哥servlet,我们的dispatcher servlet,它的映射url也是/。
registration.addMapping("/");
DefaultServlet的优先级是最低的,所以说,Dispatcher Servlet直接替代了DefaultServlet。
这样逻辑就清楚了。
我们不动tomcat的web.xml。
在mvc的配置类直接启用DefaultServlet。
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
public class DefaultServletHandlerConfigurer
extends Object
Configures a request handler for serving static resources by forwarding the request to the Servlet container’s “default” Servlet. This is intended to be used when the Spring MVC DispatcherServlet is mapped to “/” thus overriding the Servlet container’s default handling of static resources.
Since this handler is configured at the lowest precedence, effectively it allows all other handler mappings to handle the request, and if none of them do, this handler can forward it to the “default” Servlet.
文档解释的情况和我这里遇到的情况是一样的。
注册了default servlet handler之后,静态页面也能访问了。这样又没有动tomcat的web.xml,方式还是很优雅的。
对于静态资源,spring有更优雅的方式处理。我们注释掉对default servlet handler的注册:
/* @Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}*/
然后使用ResourceHttpRequestHandler来处理静态资源。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resource/**")
.addResourceLocations("/resource/");
}
我们把resource什么什么的路径全部映射到resource文件夹下。这个文件夹的位置为:webapp/resource。
在 webapp/resource 这个文件夹下,我放一个test.html
。
这时的请求路径为:
http://localhost:8088/spring_source_war/resource/test.html
我们得到了正确的响应。