SpringMVC源码分析(二)——ContextLoaderListener

                                        ContextLoaderListener继承结构图

标题

  一、对于SpringMVC功能实现的分析,我们首先从web.xml开始,在web.xml文件中我们首先配置的就是ContextLoaderListener,那么它所提供的功能有哪些又是如何实现的呢?

        当我们使用编程方式的时候我们可以直接将Spring配置信息作为参数传入Spring容器中,如:

ApplicationContext ac = new ClassPathXmlApplicationContext("applicatoinContext.xml)

我们通常使用context-param的方式注册并使用ContextLoaderListener进行监听读取。

 <!--注册spring的监听器-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法,使用ServletContextListener接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意的对象。这个对象在ServletContext启动的时候被初始化,然后在ServletContext整个运行期间都是可见的。ServletContextListener中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中。

二、ServletContextListener的使用

(1)ServletContext启动之后会调用ServeltContextListener的contextInitialized方法,我们进行源码分析:

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

这里涉及到了一个常用的WebApplicationContext:在Web应用中,我们会用到WebApplicationContext,WebApplicationContext继承自ApplicationContext,在其基础上又追加了一些特定的Web的操作及属性。继续跟踪源代码:

 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!");
        } else {
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }

            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }

                        this.configureAndRefreshWebApplicationContext(cwac, 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.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
                }

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

initWebApplicationContext函数主要是体现了创建WebApplicationContext实例的一个功能架构,从函数中我们可以看到其初始化的大致步骤:

(1)WebApplicationContext存在性的验证

在配置中允许声明一次ServlerContextListener,多次声明会扰乱Spring的执行逻辑,所以这里首先做的就是对此验证,在Spring中如果创建WebApplicationContext实例会记录在ServletContext中以方便全局调用,而使用的key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以验证的方式就是查看ServletContext实例中是否有对应key的属性。

(2)创建WebApplicationContext实例。

如果通过验证,则Spring将创建WebApplicationContext实例的工作委托给了createWebApplicationContext函数。源码如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }

    protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }

其中,在ContextLoader类中有这样的静态代码块:

  static {
        try {
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }

根据以上静态代码块的内容,我们推断在当前类ContextLoader同样目录下必定会存在属性文件ContextLoader.properties,查看后果然存在,内容如下:

org.Springframework.web.context.WebApplicationContext=org.Springframework.web.context.
    support.XmlWebApplicationContext
public static Properties loadProperties(Resource resource) throws IOException {
        Properties props = new Properties();
        fillProperties(props, resource);
        return props;
    }

    public static void fillProperties(Properties props, Resource resource) throws IOException {
        InputStream is = resource.getInputStream();

        try {
            String filename = resource.getFilename();
            if (filename != null && filename.endsWith(".xml")) {
                props.loadFromXML(is);
            } else {
                props.load(is);
            }
        } finally {
            is.close();
        }

    }

loadProperties是一个普通的加载资源文件方法

 private static final String XML_FILE_EXTENSION = ".xml";

用于加载:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
 

综合以上代码分析,在初始化的过程中,程序首先会读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。

(3)将实例记录在servletContext中。

(4)映射当前的类加载器与创建的实例到全局变量currentContextPerThread中。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值