浅析Spring WebApplicationContext加载及springmVC加入过程

想努力成长,欢迎点评。
1.首先介绍tomcat的ServletContext:每个web项目只有一个ServletContext,用于全局配置;
所谓全局配置是因为每个servlet也有自己的独立context,每个servlet可以获取全局的父ServletContext,但是父类却获取不到子类的,这是tomcat的规定,同时他也规定凡是实现ServletContextListener接口的,tomca容器在加载web.xml 上下文环境时会调用该类contextInitialized方法,并以ServletContextEvent为参数。而ServletContextEvent携带了web.xml资源的必要Filter、contextLocation的信息。
获取到ServletContextEvent并探查其中究竟
接下来开始读ContextLoaderListener的初始化代码

//根据输入入的ServletContext信息进行Spring父容器的初始化工作
this.initWebApplicationContext(event.getServletContext())

这里补充下ServletContext内容信息
ideal生成图
2.下面解析initWebApplicationContext方法

//首先判断org.springframework.web.context.WebApplicationContext.ROOT为key的容器是否已经初始化了,也就是这里规定Spring只能有一个父容器
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
        //获取日志工厂,通过上下文类加载器获取日志工厂实例对象,spi规范,获取meta/service找对应的类信息,并反射加载,这里是线程的上下文类加载器打破双亲机制的,此时日志系统就是这个时候实现的。这也就是为什么我们只需要添加jar包以及log4j配合文件就可以完成日志系统的集成,此处不加以详细描述。
            Log logger = LogFactory.getLog(ContextLoader.class);
            servletContext.log("Initializing Spring root WebApplicationContext");
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }
         //记录下启动时间
            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                //这里首先去获取servletContext.getInitParameter("contextClass")也就是web.xml中是否配置上下文容器,并根据上下文加载器去加载类,否则就会使用Spring-context默认的XmlWebApplicationContext作为ioc容器。而WebApplicationContext容器也会保存
                //request,session,globalSession,application,servletContext,contextParameters,contextAttributes等信息;
                //最后会通过反射机制,以ContextClassLoader返回一个实例类XmlWebApplicationContext类;这个类后续会介绍;
                    this.context = this.createWebApplicationContext(servletContext);
                }

//下文会介绍XmlWebApplicationContext继承关系,这里该类是自然实现了ConfigurableWebApplicationContext因此程序进入
                if (this.context instanceof ConfigurableWebApplicationContext) {
                //强转
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                        	//tomcatc初次启动,spring即监听,自然找不到父类的。
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
						//这里会处理在configLocationParam在web.xml配置的spring配置文件信息,并进行加载初始化容器,这里下文会继续深入此方法。
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
				//在这里向全局参数注入spring的上下文信息,下面是一些逻辑,就是spring容器加载了多久了,当前的类加载器是否为当前上下文类加载器了等等,不加深入了。
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
                }

                if (logger.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
                }

                return this.context;
            } catch (RuntimeException var8) {
                logger.error("Context initialization failed", var8);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
                throw var8;
            } catch (Error var9) {
                logger.error("Context initialization failed", var9);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
                throw var9;
            }
        }

现在补充介绍XmlWebApplicationContext类,根据关系图可见
XmlWebApplicationContext继承关系图
那么根据上图可知该类具有读取Spring配置文件的功能,及ResourceLoader;加载读取管理Bean及Factory,并且能够感知一切实体Bean变动的能力,而且继承了了生命周期可见其能力作用之大。
3、这里继续深入 this.configureAndRefreshWebApplicationContext(cwac, servletContext),当解析到配置信息后又是如何加载初始化容器的呢?

ContextLoader.class
 String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
            
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        //获取web.xml中配置的contextConfigLocation信息
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }
		//初始化ioc基础环境
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
        //这里会进行容器befactory的初始化,以及bean的读取
        wac.refresh();

4、 wac.refresh()解析

 public void refresh() throws BeansException, IllegalStateException {
 //枷锁,听说后续会有dispatchServlet初始化时也会调用该方法
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            //获取beanFactory 上文程序已完成contextLocation的配置假如,现在会根据这些先前信息加入生成工厂类,这里会会在工厂类中扫描配置文件,并记录所有的包名+类名的全信息,但是此时并未真正装填实例化bean信息
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

在 this.obtainFreshBeanFactory()的时候已经完成了对基本路径的扫描,bean的扫描,跟进去源码是获取各个文件的内容信息了,这个过程只是信息的收集过程,扫描xml信息内容
bean工厂信息已经就绪
5. this.registerBeanPostProcessors(beanFactory);则是获取bean的解析器,装载入bean容器了。首先是通过实现Bean工厂存储的,而这些信息放入的内容存储在ConcurrentHashMap,他是线程安全的,线程安全doug lea写的;

 private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap(16);
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256);
    private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap(64);
    private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap(64);

6.紧接着各种逻辑获取装栽逻辑bean,满眼望去印象最大的就是getBean\for\BeanPostProcessor本的解析器记住BeanPostProcessor会在aop的作用派上大用处,spring也就是依靠他来实现aspecj的装栽。具体不宜深究,今天主要目的是为了搞清楚spring web\tomcat的装栽流程如对ioc感兴趣建议自行百度,或后期更新。

 public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
       String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
       int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
       beanFactory.addBeanPostProcessor(new PostProcessorRegistrationDelegate.BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
       List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList();
       List<BeanPostProcessor> internalPostProcessors = new ArrayList();
       List<String> orderedPostProcessorNames = new ArrayList();
       List<String> nonOrderedPostProcessorNames = new ArrayList();
       String[] var8 = postProcessorNames;
       int var9 = postProcessorNames.length;

       String ppName;
       BeanPostProcessor pp;
       //装栽bean解析器并且根据优先级排好队。
       for(int var10 = 0; var10 < var9; ++var10) {
           ppName = var8[var10];
           if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
               pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
               priorityOrderedPostProcessors.add(pp);
               if (pp instanceof MergedBeanDefinitionPostProcessor) {
                   internalPostProcessors.add(pp);
               }
           } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
               orderedPostProcessorNames.add(ppName);
           } else {
               nonOrderedPostProcessorNames.add(ppName);
           }
       }

       sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
       //注册解析器
       registerBeanPostProcessors(beanFactory, (List)priorityOrderedPostProcessors);
       List<BeanPostProcessor> orderedPostProcessors = new ArrayList();
       Iterator var14 = orderedPostProcessorNames.iterator();

       while(var14.hasNext()) {
           String ppName = (String)var14.next();
           BeanPostProcessor pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
           orderedPostProcessors.add(pp);
           if (pp instanceof MergedBeanDefinitionPostProcessor) {
               internalPostProcessors.add(pp);
           }
       }

       sortPostProcessors(orderedPostProcessors, beanFactory);
       registerBeanPostProcessors(beanFactory, (List)orderedPostProcessors);
       List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList();
       Iterator var17 = nonOrderedPostProcessorNames.iterator();

       while(var17.hasNext()) {
           ppName = (String)var17.next();
           pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
           nonOrderedPostProcessors.add(pp);
           if (pp instanceof MergedBeanDefinitionPostProcessor) {
               internalPostProcessors.add(pp);
           }
       }

       registerBeanPostProcessors(beanFactory, (List)nonOrderedPostProcessors);
       sortPostProcessors(internalPostProcessors, beanFactory);
       registerBeanPostProcessors(beanFactory, (List)internalPostProcessors);
       beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    7.这段代码将来可以研究,会循环注入bean,调试进入看到 AbstractAutowireCapableBeanFactory。applyPropertyValues有两层while循环,这里循环扫描基本包了,也就是说这个方法内部实现了全部bean 注解类的的加载,本文不在研究设计模式和算法,姑且糊涂一回(其实是菜)。
  sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
                     public Object getObject() throws BeansException {
                         try {
                             return AbstractBeanFactory.this.createBean(beanName, mbd, args);
                         } catch (BeansException var2) {
                             AbstractBeanFactory.this.destroySingleton(beanName);
                             throw var2;
                         }
                     }
                 });

8.Spring父类容器已经渲染完毕了,上述提到ServletContextEvent携带了web.xml资源的必要Filter、contextLocation的信息,那么我们web.xml配置的servlet又是何时登场呢?上文提到tomcat初始化上下文后,就会开始加载servlet并执行init的方法,并设置拦截url应用内截取片段的范围。
首先看图DispatcherServlet.class,这是类和传统的Servlet的功能基础上进行了功能扩展,而当Tomcat容器初始化Servlet容器的时候,会调用Servlet的init方法。
DispatcheServlet类继承关系
而FrameworkServlet的init方法恰好就是初始化spring FrameWorkServlet对应ContextParam的 mvc配置。 this.webApplicationContext = this.initWebApplicationContext();方法是初始化子servlet容器,并设置父容器是WebapplicationContext因为他可以拿到父容器的公共bean并根据配置文件完成视图解析

 protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + this.getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]");
        }

        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);//拿到父类的容器
            wac.setConfigLocation(this.getContextConfigLocation());//同样的步骤设置自己的contextConfigLocation信息
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

9.如前文一样ConfigurableWebApplicationContext配置好自己的独属于DispatcherServlet工厂类存储好自己的配置信息后,会以责任链方式触发一个事件ContextRefreshedEvent,事件中包含很多有用信息如图
在这里插入图片描述
最后会触发DispatcherServlet.onRefresh方法,继而调用 this.initStrategies(context);
9.DispatcherServlet.initStrategies(context),接下来就是spring mvc的基本组件登场了,适配器,视图解析器等等精力问题下次陈述吧。

      this.initMultipartResolver(context);
       this.initLocaleResolver(context);
       this.initThemeResolver(context);
       this.initHandlerMappings(context);//控制器地址保存
       this.initHandlerAdapters(context);
       this.initHandlerExceptionResolvers(context);
       this.initRequestToViewNameTranslator(context);
       this.initViewResolvers(context);
       this.initFlashMapManager(context);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值