ContextLoaderListener到refresh之间的过程

web.xml 中spring的配置:

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationcontext*.xml</param-value>
    </context-param>

 

所以这个入口是ContextLoaderListener对象调用的#contextInitialized 方法:

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

 

 

 由于继承关系,所以要先初始化ContextLoader

ContextLoader中有这样一段静态代码块:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
        }
    }

defaultStrategies这个配置文件是:

 

 

 

 

 

回到ContextLoaderListener:

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    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!");
        }

        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 {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
          // 1. 这里就是获得XmlWebApplicationContext实例
this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); }
            // 2. 这里是配置和刷新webApplicationContext 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 (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }

 

进1:

    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);
        }
    }

由于没配contextClass, 所以拿到的contextClassName是null, 也就是拿defaultStrategies的配置,也就是上面的XmlWebApplicationContext值, 然后Class.forName拿到XmlWebApplicationContext.  

BeanUtils.instantiateClass, 就是创建XmlWebApplicationContext实例

 

 

 

所以进1返回的是XmlWebApplicationContext实例

进2 看configureAndRefreshWebApplicationContext方法:

要弄清楚这个方法的入参cwac, 其实是一个XmlWebApplicationContext , 因为 this.context在上面已经看到了,是XmlWebApplicationContext,  只是将它强转成了ConfigurableWebApplicationContext

 

 

 

 

 

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            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);
     // 3. 获取web.xml中 contextConfigLocation的值 /WEB-INF/applicationcontext*.xml
      String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); 
    
if (configLocationParam != null) {
       // 4 设置wac的ConfigLocaion 为:/WEB-INF/applicationcontext*.xml wac.setConfigLocation(configLocationParam); }
// The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh

// 5 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) {
        // 6 替换env中的源数据为ServletContext的源数据 ((ConfigurableWebEnvironment) env).initPropertySources(sc,
null); }      // 7. 自定义 customizeContext(sc, wac);
     // 8. spring的主入口 wac.refresh(); }

 

进3, 4 :

就是解析web.xml中的contextConfigLocation, 然后设置给was

 

 

 这一步我源码有点问题没解决,就不进去看了。

 

进5:

这里我绕了很久一开始没看懂。要注意继承关系:

先看看XmlWebApplicationContext的继承关系 ,关注这几个:

 

 

 

 

 

 

 

 

wac.getEnvironment()  虽然形参是ConfigurableWebApplicationContext, 但是实际上它是XmlWebApplicationContext(前面特意解释过)

子类没有的方法去父类中找,所以找到了AbstractApplicationContext的getEnvironment,

 

 

 在里面调用createEnvironment 方法的时候,又是对XmlWebApplicationContext对象 调用 createEnvironment方法,子类中没有的往上找,所以找到的是AbstractRefreshWebApplicationContext的createEnvironment方法:

看看这个new StandardServletEnvironment , 先看StandardServletEnvironment 继承关系

 

 

 所以看看AbstractEnvironment的构造方法:

    public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
    }

这里的this是StandardServletEnvironment对象, 所以调用StandardServletEnvironment的customizePropertySources方法:

这里两个addLast是添加属性源作为占位符

 

 

 进6 就不讲了,实际就是从ServletContext拿到source, 给env塞两个实际的source进去

 

 

 7 自定义上下文:

    protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
                determineContextInitializerClasses(sc);

        for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
            Class<?> initializerContextClass =
                    GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
            if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
                throw new ApplicationContextException(String.format(
                        "Could not apply context initializer [%s] since its generic parameter [%s] " +
                        "is not assignable from the type of application context used by this " +
                        "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
                        wac.getClass().getName()));
            }
            this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
        }

        AnnotationAwareOrderComparator.sort(this.contextInitializers);
        for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
            initializer.initialize(wac);
        }
    }

这里就两部,拿到所有的ApplicationContextInitializer ,然后调用initialize方法

从web.xml中拿到param-name : contextInitializerClasses 对应的value, 

 

 

 

 

所以这里可以自定义扩展, 在was.refresh之前做一些对XmlWebApplicationContext做一些自定义操作

以下是代码示例:

web.xml中添加:

    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.readspring.MyApplicationContextInitializer</param-value>
    </context-param>

自定义类实现ApplicationContextInitializer接口,这里就只打印一句话示例:

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer");
    }
}

结果:

 

 进8 was.refresh就是ioc的主入口了,  完。

 

 


 

posted @ 2020-11-18 01:04  圣金巫灵  阅读(138)  评论(0编辑  收藏  举报