Spring静态资源处理多种方式(<mvc:default-servlet-handler>)
可以基于xml或纯java配置, 源码分析基于Spring 5.2.5, 为了简洁会省去部分源码.
简单来说DispatcherServlet接收了所有请求(“/”), 交由某个映射器来处理静态资源的请求(forward到tomcat默认servlet 或 直接resource写入)
方式一:<mvc:default-servlet-handler>
// 所有<mvc:xxx>由MvcNamespaceHandler管理,从该类获取对应处理类
class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {
//...只剩下关键代码
String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
//...
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
//...
Map<String, String> urlMap = new ManagedMap<>();
urlMap.put("/**", defaultServletHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
//....
}
从中可以知道注册了两个bean, SimpleUrlHandlerMapping
(HandlerMapping)和**DefaultServletHttpRequestHandler
**(最终变成该映射器的handler).
从上面第11行 /**
可知, SimpleUrlHandlerMapping会在DispatcherServlet中拦截所有请求(一般位于映射器列表末尾,起到“保底”作用), 最终交由DefaultServletHttpRequestHandler#handleRequest处理, forward到tomcat默认servlet(下面第7行).
// 可以看出只是单纯转发, 最终还是交由tomcat等容器的默认servlet处理(默认的servlet="defalut")
// 该servlet名字可自行指定, <mvc:default-servlet-handler default-servlet-name="">
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
rd.forward(request, response);
}
}
要注意的是拦截器的依然会生效(先执行HandlerInterceptor#preHandle, 后执行该代码)
方式二:<mvc:resources>
<mvc:resources mapping="/images/**" location="/WEB-INF/images"/>
参考上面,从ResourcesBeanDefinitionParser
得知注册的关键bean为,SimpleUrlHandlerMapping
和ResourceHttpRequestHandler
.
该标签做的事情与上面类似, 不同的是可以自定义资源映射路径, 处理也不是简单forward, 是由Spring MVC自己处理Resource(可以缓存等).
public class ResourceHttpRequestHandler implements HttpRequestHandler {
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
Resource resource = getResource(request);
//...写入response
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
}
两个标签可以同时存在, 最终会生成多个映射器(SimpleUrlHandlerMapping), 注意顺序即可
以下是纯Java配置, 上面两种方式也可以配置成@Bean的形式
方式三: WebMvcConfigurer
该方式与方式一,二创建的bean的对应
@EnableWebMvc, 简单来说该注解引入的类DelegatingWebMvcConfiguration
, 最终会调用WebMvcConfigurer的方法
(具体参考另外一篇文章:<mvc:annotation-driven> 和 @EnableWebMvc 解析)
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 默认静态资源处理,对应方式一, (DefaultServletHandlerConfigurer#buildHandlerMapping)
configurer.enable();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 方式二, (源码参考:ResourceHandlerRegistry#getHandlerMapping)
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
}