本文共 22229 字,大约阅读时间需要 74 分钟。
前面的三篇文章分别讲解了配置文件注册bean的过程、注解注册bean的过程和DispatchServlet的初始化。
那现在我们就可以把目光转向我们使用SpringMVC处理静态资源时的配置的原理了。先来看一下使用
来处理静态资源的原理到底是怎样的。
在之前我们已经讲解过了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队列) MapurlMap = new ManagedMap (); //得到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; }//注册ResourceHttpRequestHandlerprivate 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 locations = new ManagedList (); 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所处理。那么具体是怎么处理的呢。我们来看一下。
在讲解怎么处理之前,我们需要来关注下这个类的实例化全过程。
同样的,我们先来看看他的继承链。
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
先瞅一下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
皇天不负有心人,终于找到了,实现了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(MapurlMap) throws BeansException { if (urlMap.isEmpty()) { logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); } else { for (Map.Entry 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框架自己来完成。
不多说废话了,直接来到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)); MapurlMap = new ManagedMap (); 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,他的具体实现我们明天再看,真的困~
转载地址:http://udmvb.baihongyu.com/