美文网首页
ContextLoaderListener的实现

ContextLoaderListener的实现

作者: 多喝水JS | 来源:发表于2017-03-28 17:53 被阅读127次

    ContextLoaderListener设计与实现

    在web项目中,Spring通过ContextLoaderListener监听器来实现在web服务器启动时载入IOC容器。而ContextLoaderListener则通过使用ContextLoader来实现IOC容器的初始化工作。下面来看看ContextLoaderListener是如何实现的

    源码入口

    对于 Spring 的ContextLoaderListener功能实现的分析,我们首先从 web.xml 开始

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"
        xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext2.xml</param-value>
        </context-param>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
       <!-- 配置DispatchcerServlet -->
          <servlet>
            <servlet-name>springDispatcherServlet</servlet-name>
             <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
             <!-- 配置Spring mvc下的配置文件的位置和名称 -->
             <init-param>
                 <param-name>contextConfigLocation</param-name>
                 <param-value>classpath:applicationContext.xml</param-value>
             </init-param>
             <load-on-startup>1</load-on-startup>
         </servlet>
         
         <servlet-mapping>
             <servlet-name>springDispatcherServlet</servlet-name>
             <url-pattern>/</url-pattern>
         </servlet-mapping>
    </web-app>   
    

    上面的配置可以看到,当服务器启动时,会触发ContextLoaderListener监听器,ContextLoaderListener将会加载classpath目录下的applicationContext2.xml文件进行解析、注册并注入bean。

    ContextLoaderListener启动过程

    当服务器启动时【这里以Tomcat服务器为例】,会进入Tomcat下的Bootstrap类的main方法,并启动各个组件,具体的调用过程就不写了,有空再写篇Tomcat服务器启动过程文章。
    Bootstrap类的main方法:

      public static void main(String args[]) {
            if (daemon == null) {
                       Bootstrap bootstrap = new Bootstrap();
                       bootstrap.init();
                         daemon = bootstrap;
                  }
            try {
                String command = "start";
                if (args.length > 0) {
                    command = args[args.length - 1];
                }
              else if (command.equals("start")) {
                    daemon.setAwait(true);
                    daemon.load(args);
                    daemon.start();//这里开始启动各个组件
                } 
        }   
    

    最终调用到StandardContext类的startInternal方法,这个方法比较长,我就截关键部分的代码:

        protected synchronized void startInternal() throws LifecycleException {
            //前面的代码省略
                if (ok) {
                    //这里开始执行监听器的启动过程
                    if (!listenerStart()) {
                        log.error( "Error listenerStart");
                        ok = false;
                    }
                }
               //后面的代码也省略了; 
           
        }
    

    最后执行所有实现了ServletContextListener监听器的类。
    看下ContextLoaderListener的接口

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener
    

    所以将执行ContextLoaderListener类中的contextInitialized方法

    @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }
    

    我们接着来看initWebApplicationContext(event.getServletContext());方法,这个将进行Spring的IOC的解析,注册,注入的过程
    进去这个initWebApplicationContext(event.getServletContext());方法
    ,这个方法是在ContexLoader类中实现的

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
              //如果容器已经被初始化将抛出异常,也就是容器只能初始化一次
            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!");
            }
            try {
            //如果容器为空,这里将创建一个容器,也就是WebApplicationContext容器
                if (this.context == null) {
                    this.context = createWebApplicationContext(servletContext);
                }
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                    if (!cwac.isActive()) {
                        
                        if (cwac.getParent() == null) {
                        
                            ApplicationContext parent = loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
            //执行解析、注册,注入过程
                configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
    //把新创建的容器存放到servletContext容器中   
                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 + "]");
                }
                
    
                return this.context;
            }
            catch (RuntimeException ex) {
                logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
                throw ex;
            }
            catch (Error err) {
                logger.error("Context initialization failed", err);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
                throw err;
            }
        }
    

    从上面的代码可以看出,分为三部,首先创建容器,这个容器是WebApplicationContext容器,然后通过容器来执行bean的解析,注册和注入过程,最后把WebApplicationContext容器存放到servletContext上下文中以备用。
     最后来看下容器进行bean的解析、注册,注入过程吧
    首先进入configureAndRefreshWebApplicationContext方法

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
            if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
                
                String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
                if (idParam != null) {
                    wac.setId(idParam);
                }
                else {
                    // Generate default id...
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()));
                }
            }
    
            wac.setServletContext(sc);
    //这里拿到web.xml配置文件中配置的applicationContext2.xml文件
            String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
            if (configLocationParam != null) {
                wac.setConfigLocation(configLocationParam);
            }
    
            
            ConfigurableEnvironment env = wac.getEnvironment();
            if (env instanceof ConfigurableWebEnvironment) {
                ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
            }
    
            customizeContext(sc, wac);
    //关键步骤,这里将执行bean的解析和注册,注入过程
            wac.refresh();
        }
    

    由于解析注册注入过程比较复杂,以后有空再专门写一篇吧,先到这了。

    后记

    这是我自己看源码总结的,错误之处请帮忙指出,大家一起进步。

    [朝阳区尼克杨]
    转载请注明原创出处,谢谢啦!

    相关文章

      网友评论

          本文标题:ContextLoaderListener的实现

          本文链接:https://www.haomeiwen.com/subject/jdajottx.html