那些實現了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方法內調用

相關文章
相關標籤/搜索