博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC配置文件详解(四)
阅读量:2351 次
发布时间:2019-05-10

本文共 22229 字,大约阅读时间需要 74 分钟。


前言

前面的三篇文章分别讲解了配置文件注册bean的过程、注解注册bean的过程和DispatchServlet的初始化。

那现在我们就可以把目光转向我们使用SpringMVC处理静态资源时的配置的原理了。先来看一下使用

来处理静态资源的原理到底是怎样的。

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
urlMap = 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所处理。那么具体是怎么处理的呢。我们来看一下。

在讲解怎么处理之前,我们需要来关注下这个类的实例化全过程。

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() {                @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() {                @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
urlMap) 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框架自己来完成。

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
urlMap = 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/

你可能感兴趣的文章
RichEdit对ole 对象的相关支持总结
查看>>
(分享)win10下双显示屏独立设置不同缩放率的方法
查看>>
管理学十大经典定理
查看>>
杨澜的一句话,却要让我记一生
查看>>
U盘使用心得
查看>>
作为程序员的心态
查看>>
struts 2 s:if标签的使用
查看>>
input 按钮背景,在IE6 IE7中不显示
查看>>
div使用margin:0px auto 不居中
查看>>
JavaScript 事件模型 事件处理机制
查看>>
Invalid character constant
查看>>
CSS浏览器兼容性问题 归纳
查看>>
Java:Java快速入门[转]
查看>>
javascript中的变量作用域
查看>>
margin折叠的问题
查看>>
http状态头列表
查看>>
CSS hack 收集
查看>>
Markdown 语法
查看>>
前端工程师面试考察要点
查看>>
前端面试题——js闭包
查看>>