前言
前面的三篇文章分别讲解了配置文件注册bean的过程、注解注册bean的过程和DispatchServlet的初始化。
那现在我们就可以把目光转向我们使用SpringMVC处理静态资源时的配置的原理了。先来看一下使用
<mvc:resources location="/public/static/*" mapping="/static/*"/>
来处理静态资源的原理到底是怎样的。
mvc:resource的解析
在之前我们已经讲解过了context:component-scan的解析过程。这两个标签都属于扩展标签。
他们的解析都是使用BeanDefinitionParser的相应实现类来解析的,只是他们对应着不同的实现类。
context:component-scan对应着的是ComponentScanBeanDefinitionParser,而mvc:resource对应着的是org.springframework.web.servlet.config.ResourcesBeanDefinitionParser。
至于如何找到这个类等问题我就不再说了,直接看这个类的parser方法
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerUrlProvider(parserContext, source);
//得到location属性指定的字符串
String resourceHandlerName = registerResourceHandler(parserContext, element, source);
if (resourceHandlerName == null) {
return null;
}
//new一个urlMap,继承了LinkedHashMap(可以用来做FIFO队列)
Map<String, String> urlMap = new ManagedMap<String, String>();
//得到mapping属性指定的字符串
String resourceRequestPath = element.getAttribute("mapping");
if (!StringUtils.hasText(resourceRequestPath)) {
parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
return null;
}
//以mapping的值为key 以location的值为value,注册到这个urlMap中,
urlMap.put(resourceRequestPath, resourceHandlerName);
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);
//生成一个包含SimpleUrlHandlerMapping实例的BeanDefinition
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//注册SimpleUrlHandlerMapping的urlMap属性
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
//如果有指定mvcPathMatcher和mvcUrlPathHelper属性的话,就会注册这两个属性,默认是无,不是重点,先不分析
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef).add("urlPathHelper", pathHelperRef);
String order = element.getAttribute("order");
// Use a default of near-lowest precedence, still allowing for even lower precedence in other mappings
//如果没有指定order,则把order值指定为MAX-1
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
//将SimpleUrlHandlerMapping实例注册到BeanFactory中
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
// Register HttpRequestHandlerAdapter
//注册默认的mapping adapter等
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
return null;
}
//注册ResourceHttpRequestHandler
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
return null;
}
ManagedList<String> locations = new ManagedList<String>();
locations.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(locationAttr)));
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", locations);
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
}
Element resourceChainElement = DomUtils.getChildElementByTagName(element, "resource-chain");
if (resourceChainElement != null) {
parseResourceChain(resourceHandlerDef, parserContext, resourceChainElement, source);
}
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
return beanName;
}
这个方法具体做了什么代码里已经分析的比较明白了,这个方法最后就是给我们注册了一个SimpleUrlHandlerMapping实例到Spring容器中,并且为这个实例注入了urlMap属性和order属性。
很明显这个实例会在初始化DispatchServlet的时候注入到该类的handlerMappings属性中。
而此时,当一个对静态资源的请求过来时,他依旧还是会走DispatchServlet,但是这个时候,就不会再产生404 not found错误了。这是为什么呢?因为虽然在RequestMappingHandlerMapping(oreder为0)和BeanNameUrlHandlerMapping(order为2)中都找不到对应的Handler,
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
但是此时的handlerMappings中多了一个成员,那就是SimpleUrlHandlerMapping(order为Integer.MAX-1),当静态资源的位置是我们在标签中配置好的location的时候,就能被这个Mapping所处理。那么具体是怎么处理的呢。我们来看一下。
在讲解怎么处理之前,我们需要来关注下这个类的实例化全过程。
SimpleUrlHandlerMapping的实例化
同样的,我们先来看看他的继承链。
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered {
public abstract class WebApplicationObjectSupport extends
ApplicationObjectSupport implements ServletContextAware {
public abstract class ApplicationObjectSupport implements ApplicationContextAware {
public interface ServletContextAware extends Aware {
哎,我们发现他的祖先类实现了Aware接口,哎哟喂,路子挺野的呀。这个我们上一篇讲过了。直接拿过来用啦。
直接看AbstractAutowireCapableBeanFactory的initializeBean方法
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
先瞅一下invokeAwareMethods方法
private void invokeAwareMethods(final String beanName, final Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
蛤?并没有我们这里的接口诶,虽然我们的接口也继承了Aware,但是在这没卵用呀,我们的是ApplicationContextAware和ServletContextAware,这两在这都不管用呀,是不是搞错地方了呀。
别着急,千万别着急,我们还漏了一个地方没有讲。
我们暂时把目光回到AbstractApplicationContext的prepareBeanFactory方法一下下
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
}
在这个方法中有一个方法叫addBeanPostProcessor,我们进入这个方法看看。
@Override
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");
this.beanPostProcessors.remove(beanPostProcessor);
this.beanPostProcessors.add(beanPostProcessor);
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
this.hasInstantiationAwareBeanPostProcessors = true;
}
if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) {
this.hasDestructionAwareBeanPostProcessors = true;
}
}
这个方法的作用即添加了一个ApplicationContextAwareProcessor实例到BeanFactory的BeanPostProcessor集合中。
我们再回到initializeBean方法,我们已经证明invokeAwareMethods没有对我们这个类实现的接口有什么处理,我们继续往下看,会看到applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);方法,我们来看下这个方法
applyBeanPostProcessorsBeforeInitialization
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
这里对BeanFactory的BeanPostProcessor集合进行了遍历,并调用他们的postProcessBeforeInitialization方法,我们已经知道了这个集合中有ApplicationContextAwareProcessor实例,我们来看下他的这个方法。
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareInterfaces(bean);
return null;
}
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
皇天不负有心人,终于找到了,实现了ApplicationContextAware接口的Bean,在初始化的过程中,都会调用他的setApplicationContext方法。
现在我们就来看看
我们发现setApplicationContext只有在祖先类ApplicationObjectSupport中有实现。
@Override
public final void setApplicationContext(ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
// Reset internal context state.
this.applicationContext = null;
this.messageSourceAccessor = null;
}
else if (this.applicationContext == null) {
// Initialize with passed-in context.
if (!requiredContextClass().isInstance(context)) {
throw new ApplicationContextException(
"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
}
this.applicationContext = context;
this.messageSourceAccessor = new MessageSourceAccessor(context);
initApplicationContext(context);
}
else {
// Ignore reinitialization if same context passed in.
if (this.applicationContext != context) {
throw new ApplicationContextException(
"Cannot reinitialize with different application context: current one is [" +
this.applicationContext + "], passed-in one is [" + context + "]");
}
}
}
而其中最重要的方法initApplicationContext方法在该类中是个空方法,也就是说这个方法是留个子类来实现的。
下面的类都实现了这个方法
//WebApplicationObjectSupport
@Override
protected void initApplicationContext(ApplicationContext context) {
super.initApplicationContext(context);
if (this.servletContext == null && context instanceof WebApplicationContext) {
this.servletContext = ((WebApplicationContext) context).getServletContext();
if (this.servletContext != null) {
initServletContext(this.servletContext);
}
}
}
//AbstractHandlerMapping
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
//SimpleUrlHandlerMapping
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
所以在调用SimpleUrlHandlerMapping的initApplicationContext会调用AbstractHandlerMapping的initApplicationContext方法,而他的这个方法主要是初始化拦截器,这个不是我们现在要说的重点,这个以后会细说,拦截器是很多框架中的一个重要组成部分。
我们现在就直接看SimpleUrlHandlerMapping的initApplicationContext方法中的 registerHandlers(this.urlMap);
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
}
}
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
private String getHandlerDescription(Object handler) {
return "handler " + (handler instanceof String ? "'" + handler + "'" : "of type [" + handler.getClass() + "]");
}
代码不难理解,就是讲SimpleUrlHandlerMapping中的urlMap的匹配信息转到AbstractUrlHandlerMapping中的handlerMap中。这样就完成了注册。
说完了这两个HandlerMapping的实例化,那你对没有讲到的BeanNameUrlHandlerMapping的初始化流程也不会觉得难了。
讲解完了初始化之后,又该讲到处理请求了。
getHandler得到的会是一个ResourceHttpRequestHandler实例。
通过AbstractUrlHandlerMapping的lookupHandler得到,不难理解吧,看下源码很容易得出。什么细节都讲的话太多了。
然后就要看ha.supports(handler);到底选中哪个了,ResourceHttpRequestHandler实现了HttpRequestHandler接口,所以是HttpRequestHandlerAdapter。
handle方法:
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
很明显,我们处理的是一个静态资源,所以返回一个空的ModelAndView对象。
所以真正的处理就全在ResourceHttpRequestHandler的handleRequest方法里了。
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
checkAndPrepare(request, response, true);
// check whether a matching resource exists
Resource resource = getResource(request);
if (resource == null) {
logger.trace("No matching resource found - returning 404");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// check the resource's media type
MediaType mediaType = getMediaType(resource);
if (mediaType != null) {
if (logger.isTraceEnabled()) {
logger.trace("Determined media type '" + mediaType + "' for " + resource);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No media type found for " + resource + " - not sending a content-type header");
}
}
// header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304");
return;
}
setHeaders(response, resource, mediaType);
// content phase
if (METHOD_HEAD.equals(request.getMethod())) {
logger.trace("HEAD request - skipping content");
return;
}
writeContent(response, resource);
}
使用\标签,就把吹静态资源的任务交给了ResourceHttpRequestHandler,由SpringMVC框架自己来完成。
mvc:default-servlet-handler的解析
不多说废话了,直接来到DefaultServletHandlerBeanDefinitionParser的parse方法
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(defaultServletName)) {
defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
}
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put("/**", defaultServletHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
return null;
}
很显然是将静态资源交由web容器的defaultservlet来处理,这种方式的优先级是最低的,他的order是Integer.MAX,他的具体实现我们明天再看,真的困~