那些实现了Spring接口的类,都是怎么被加载的

1.ApplicationContextInitializer接口

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support * for declaring a "contextInitializerClasses" context-param and init-param, respectively. * * <p>{@code ApplicationContextInitializer} processors are encouraged to detect * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}复制代码

这是一个Spring接口,实现该接口后,spring会自动加载实现类并执行initialize方法。除了实现这个接口,还须要在web.xml中配置一个<context-param>参数,<param-name>必须是contextInitializerClasses。java

<context-param>
	<param-name>contextInitializerClasses</param-name>
	<param-value>com.xxx.XXXApplicationContextInitializer</param-value>
</context-param>复制代码

那这个流程是怎么一回事呢?web


因而我写了一个demo开始debug:spring

public class MySimpleContextListener implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("开始加载了");
    }

}复制代码


这是调用栈,从下往上,是代码的执行顺序。bash


首先确定是Tomcat被启动,而后bla……bla……bla……boom……da...da...da(此处略过我不懂的启动流程)。app

Tomcat启动完以后开始启动web程序,并按照java web的启动顺序,依次加载,以下:ide

ServletContext -> context-param -> listener -> filter -> servletpost

通常用springMVC构建的web程序,web.xml里都会设置这个监听器ui

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>复制代码

也就是调用栈里的ContextLoaderListener类,该类又继承org.springframework.web.context.ContextLoader。在调用ContextLoaderListener类的contextInitialized()方法时,又会调用其父类的initWebApplicationContext()方法,以下:this

public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}复制代码

而后这个方法再调别的方法,一层一层,总之,就是调用栈里的那个顺序。spa

最后,目光放到ContextLoader类的customizeContext()方法:

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);
        }
    }复制代码


全在这里面了,如今能够一行行的看。

List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc);

它的做用是从web.xml里提取名为“globalInitializerClasses”及“contextInitializerClasses”的<context-param>参数的值。这些值在web.xml里都是字符串,因此还须要转成Class<?>对象,转换的过程当中会检查,配置的这个类名是否是实现了ApplicationContextInitializer接口的类,不是的话会抛出异常。

紧接着的for循环是检查配置的这些实现类的泛型,是否是ConfigurableApplicationContext。不是的话,会抛出异常。是的话,会被实例化成对象,并放入List中。

最后,for循环执行List中ApplicationContextInitializer接口的对象,调用initialize()方法。


// 全部代码看完,发现这一切的流程早被写在接口的注释里,因此说英语好是多么重要。。。。。。


2.BeanPostProcessor接口

这个接口的做用就是在bean的初始化前或初始化后搞一些操做。

必须把BeanPostProcessor接口实现类的对象放到容器里去才会起效,往容器添加bean有两种方式,一,xml;二,注解。

大体的逻辑,spring保存了一个List<BeanPostProcessor>对象,并在bean初始化时,for循环这个list,依次调用。

这个逻辑很简单,复杂的是,spring怎么把BeanPostProcessor接口实现类的对象放到容器里去的?

上面的ApplicationContextInitializer接口经过web.xml配置,其实现类在容器启动前便被初始化了。而BeanPostProcessor接口从加载到起效,几乎经历了完整的spring容器启动过程。

所以,要搞懂BeanPostProcessor接口如何被加载,就必须搞明白spring容器是如何加载的。

spring容器的启动与加载是一个至关繁琐的过程,记录它须要新开一篇博客,并查阅资料,而后更加深刻的阅读源码。这里只能先就BeanPostProcessor接口起效的调用栈,来粗略的讲解一下。


与ApplicationContextInitializer接口相比,这个调用过程,明显长的多。固然开头几个调用仍是同样的,从org.springframework.web.context.ContextLoaderListener进入,初始化WebApplicationContext,而后refresh,此处给出refresh方法的部分源码:

synchronized (this.startupShutdownMonitor) {	
    // Prepare this context for refreshing. 
    prepareRefresh(); 	

    // Tell the subclass to refresh the internal bean factory. 
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 
	
    // Prepare the bean factory for use in this context. 
    prepareBeanFactory(beanFactory); 	
       
    try {		
        // Allows post-processing of the bean factory in context subclasses. 
        postProcessBeanFactory(beanFactory);  		

        // Invoke factory processors registered as beans in the context. 
        invokeBeanFactoryPostProcessors(beanFactory); 		

        // Register bean processors that intercept bean creation. 
        registerBeanPostProcessors(beanFactory);  		

        // Initialize message source for this context. 
        initMessageSource();  		

        // Initialize event multicaster for this context. 
        initApplicationEventMulticaster(); 		

        // Initialize other special beans in specific context subclasses. 
        onRefresh(); 		

        // Check for listener beans and register them. 
        registerListeners();  		

        // Instantiate all remaining (non-lazy-init) singletons. 
        finishBeanFactoryInitialization(beanFactory); 		

        // Last step: publish corresponding event. 
        finishRefresh();	
    }
}复制代码

refresh方法,以及refresh方法中调用的方法,以及调用的方法中调用的方法,以及调用的方法中调用的方法调用的方法,组成了加载的过程。

// 不是我要说的这么绕,源码远比我讲的要绕。

目前我了解的过程主要集中在读取spring配置文件上(就是那些xml)。根据标签类型来进行处理,并封装成org.springframework.beans.factory.config.BeanDefinition,最后放到org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap中。

详情新开博客讲。


3.PropertyPlaceholderConfigurer

和BeanPostProcessor接口同样,调用的逻辑很简单,加载的逻辑难,也要先搞明白,spring容器是如何加载的。


如图,invokeBeanFactoryPostProcessors方法,就是在refresh方法内调用

相关文章
相关标签/搜索