1. 程式人生 > 程式設計 >手把手教你實現spring-context

手把手教你實現spring-context

系列文章

手把手教你實現spring-beans (一)
手把手教你實現spring-beans (二)
手把手教你實現spring-context
手把手教你實現spring-aop (TODO)

前言

  本文是對tiny-spring專案的詳細解讀,聚焦spring-context的基本實現,對應著(seventh~ninth)-stage這三個構建過程。

引入ResourceLoader

  Spring提供的ApplicationContextBeanFactory的基礎之上,添加了幾個重要特性:資源載入、事件派發和國際化。不僅如此,相較於BeanFactory只能手動註冊服務,ApplicationContext

可以自動發現和註冊服務,比如BeanPostProcessorBeanFactoryPostProcessor,並且ApplicationContext在建立時就會一次性載入所有non lazy initJavaBean,可以說ApplicationContextBeanFactory提高了一個臺階,更易於客戶端程式的使用。

  讓我們先來看看增強的資源載入功能。在第一篇中我們說到Spring提供了一個對資源的統一抽象——Resource,並由此衍生出多個代表不同來源的Resource實現。然而,在基礎BeanFactory的使用過程中,具體使用什麼型別的Resource是需要客戶端程式手動指定的:

    Resource resource = new ClassPathResource("test/config.xml");
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    reader.loadBeanDefinitions(resource);
複製程式碼

比如這裡,我們選擇使用ClassPathResource

是通過硬編碼實現的,有沒有什麼辦法可以讓Spring自動識別資源的型別呢?為瞭解決這個問題,Spring引入了一個策略介面ResourceLoader。實際上,Spring還提供了一個ResourceLoader的子介面ResourcePatternResolver,它可以一次性載入多個資源。

// 命令列輸入 git checkout seventh-stage,在io包中可以看到對ResourceLoader的定義

    /**
     * 策略介面,用來載入資源。
     * Discussion: 可以根據location的不同表現形式,
     * 返回不同的Resource,故而稱為策略介面。
     */
    public interface ResourceLoader {
        /**
         * 載入一個資源,可以是ClassPathResource/FileSystemResource等等。
         */
        Resource getResource(String location);
    
        /**
         * 返回ResourceLoader在載入資源時使用的類載入器。
         */
        ClassLoader getClassLoader();
    }
複製程式碼

有了ResourceLoader,我們只需要提供資源的地址,至於它到底是什麼型別的資源Spring會幫我們做判斷,而不再需要我們手動指定。DefaultResourceLoader是一個ResourceLoader的實現,它根據資源地址中的協議型別來判斷具體的資源型別,程式碼比較簡單,這裡就不多說了。

引入事件機制

  BeanFactory為其持有的各種JavaBean提供了一套統一的生命週期管理(init-methodInitializingBean,etc),但客戶端程式卻沒有多少手段可以干涉到BeanFactory本身。為此ApplicationContext提供了一套事件機制,用以向客戶端程式報告ApplicationContext的程式,當然也支援派發自定義事件。實際上ApplicationContext還實現了Lifecycle介面,客戶端程式可以依此來開啟或關閉ApplicationContext,不過在我們的實現中並沒有提供這一層抽象。

  ApplicationContext中的事件處理是由ApplicationEvent類和ApplicationListener介面來提供的,兩者分別基於標準的java.util.EventObjectjava.util.EventListener,事件的派發則是通過ApplicationEventPublisher介面。Spring中事件派發機制使用觀察者模式來實現。

    /**
     * 應用程式事件派發器,封裝了事件的派發邏輯,一般作為
     * ApplicationContext的父介面使用。
     */
    public interface ApplicationEventPublisher {
        /**
         * 派發一個ApplicationEvent,並通知所有已註冊的ApplicationListener。
         */
        void publishEvent(ApplicationEvent event);
    }
複製程式碼

為了支援ApplicationEventPublisher的工作,Spring又提供了ApplicationEventMulticaster介面,它支援對ApplicationListener的加入、移除管理,還可以將收到的ApplicationEvent派發到已註冊的ApplicationListener,可以說ApplicationEventMulticaster就是ApplicationEventPublisher的一個代理。

    /**
     * 應用程式事件多播器,管理著一組ApplicationListener,並將
     * ApplicationEvent派發給它們。
     *
     * ApplicationEventPublisher就是利用ApplicationEventMulticaster
     * 來將事件派發給監聽器。
     */
    public interface ApplicationEventMulticaster {
        /**
         * 新增一個ApplicationListener來接收ApplicationEvent。
         */
        void addApplicationListener(ApplicationListener listener);
    
        /**
         * 移除一個ApplicationListener。
         */
        void removeApplicationListener(ApplicationListener listener);
    
        /**
         * 移除所有已註冊的ApplicationListener。
         */
        void removeAllListeners();
    
        /**
         * 將給定的ApplicationEvent多播到適當的ApplicationListener(s)。
         */
        void multicastEvent(ApplicationEvent event);
    }
複製程式碼

DefaultApplicationEventMulticaster實現了ApplicationEventMulticaster介面,非常簡單的一個類,各位同學可以自行翻閱。

引入ApplicationContext

  如果拋開國際化不談,講到這裡ApplicationContext的定義也就呼之欲出了:

    /**
     * 在BeanFactory之上整合許多易用的功能,更加方便客戶端的使用,比如資源載入、事件派發等等。
     */
    public interface ApplicationContext extends ResourceLoader,ListableBeanFactory,ApplicationEventPublisher {
        /**
         * 返回ApplicationContext的名稱
         */
        String getDisplayName();
    
        /**
         * 返回ApplicationContext的啟動時間
         */
        long getStartupDate();
    }
複製程式碼

上一篇提到的BeanFactory的設計思路類似,ApplicationContext也採用了分層的設計思想,其中一個比較重要的子介面是ConfigurableApplicationContext

    /**
     * 類似於BeanFactory設計,ApplicationContext也只是一個功能最小的介面,
     * ConfigurableApplicationContext在此基礎之上擴充了它的功能。
     */
    public interface ConfigurableApplicationContext extends ApplicationContext {
        /**
         * 新增一個BeanFactory後置處理器。
         */
        void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor);
    
        /**
         * 新增一個ApplicationListener用來接收ApplicationContext的開啟、關閉、重新整理等事件。
         */
        void addApplicationListener(ApplicationListener listener);
    
        /**
         * 重新整理ApplicationContext。
         */
        void refresh() throws BeansException,IllegalStateException;
    
        /**
         * 關閉當前ApplicationContext。
         */
        void close();
    
        /**
         * 返回當前ApplicationContext內部組合的BeanFactory。
         */
        ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
    }
複製程式碼

實現ApplicationContext

  AbstractApplicationContext是Spring提供的一個模板類,它實現了ConfigurableApplicationContext的大部分基礎功能。具體的,AbstractApplicationContext選擇繼承DefaultResourceLoader從而獲得了資源載入的能力,同時通過ConfigurableApplicationContext.getFactory()返回的ConfigurableListableBeanFactory滿足了對ListableBeanFactory的需求。這樣的設計,使得AbstractApplicationContext成功的將BeanFactory的建立工作留給了子類。

  前面說過,ApplicationContext在啟動時會一次性初始化所有non lazy initJavaBean,並且可以自動發現和註冊服務,這又是怎麼做到的呢?這一切的核心在於refresh()方法的實現,我們來具體看一下:

// 命令列輸入 git checkout eighth-stage

    @Override
    public void refresh() throws BeansException,IllegalStateException {
        // 記錄啟動時間
        startupDate = System.currentTimeMillis();
        // 重新整理內部BeanFactory
        ConfigurableListableBeanFactory beanFactory = refreshBeanFactory();
        // 為BeanFactory進行必要的準備工作
        prepareBeanFactory(beanFactory);
        try {
            // 進行額外的後置處理
            postProcessBeanFactory(beanFactory);
            // 執行BeanFactoryPostProcessor的回撥
            invokeBeanFactoryPostProcessors(beanFactory);
            // 註冊所有BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            // 初始化事件多播器
            initApplicationEventMulticaster();
            // 準備完成,正在重新整理
            onRefresh();
            // 註冊所有ApplicationListener
            registerApplicationListeners();
            // 完成BeanFactory的初始化流程
            finishBeanFactoryInitialization(beanFactory);
            // 完成重新整理
            finishRefresh();
        } catch (BeansException e) {
            // 若失敗,就清理資源
            beanFactory.destroySingletons();
            // 丟擲這個異常給呼叫者
            throw e;
        }
    }

複製程式碼

  其中,refreshBeanFactory()是一個模板方法,顯然我們應該在這裡重新建立BeanFactory並載入配置檔案,又因為refresh方法可以多次呼叫,所以我們也應該清理一下原來BeanFactory持有的資源(主要是快取的singleton bean)。同時為了給子類留下進一步的定製空間,又定義了鉤子方法prepareBeanFactory(...)postProcessBeanFactory(...)。提一下與ApplicationContext有關的幾個Aware介面(ResourceLoaderAwareApplicationContextAwareApplicationEventPublisherAware),它們由名為ApplicationContextAwareProcessorBeanPostProcessor來負責處理,這個BeanPostProcessor就是在prepareBeanFactory(...)中註冊的。

    public class ApplicationContextAwareProcessor implements BeanPostProcessor {
    
        private final ApplicationContext applicationContext;
    
        public ApplicationContextAwareProcessor(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean,String name) throws BeansException {
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
    
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean,String name) throws BeansException {
            return bean;
        }
    }
    
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 新增Bean後置處理器
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    }
複製程式碼

到這裡,可以說BeanFactory已經完全準備完畢,是時候為BeanFactoryPostProcessor執行回撥了。

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        // 1. 處理直接註冊的BeanFactoryPostProcessor
        for (BeanFactoryPostProcessor processor : getBeanFactoryPostProcessors()) {
            processor.postProcessBeanFactory(beanFactory);
        }
        // 2. 處理在配置檔案中配置的BeanFactoryPostProcessor
        // 2.1 獲取型別為BeanFactoryPostProcessor的所有beanName
        String[] beanNames = beanFactory.getBeanDefinitionNames(BeanFactoryPostProcessor.class);
        for (String beanName : beanNames) {
            // 2.2 在呼叫之前確保BeanFactoryPostProcessor得到初始化
            BeanFactoryPostProcessor processor = getBean(beanName,BeanFactoryPostProcessor.class);
            // 2.3 呼叫BeanFactoryPostProcessor的相關方法
            processor.postProcessBeanFactory(beanFactory);
        }
    }
複製程式碼

  這裡分為兩部分,首先是為直接註冊的BeanFactoryPostProcessor執行回撥,然後再去配置檔案中尋找BeanFactoryPostProcessor,為它們執行回撥,也就是所謂的自動發現服務。registerBeanPostProcessors(...)同理,它在配置檔案尋找BeanPostProcessor,並將尋找到的BeanPostProcessor註冊進BeanFactory

    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // ApplicationContext表面上只是一個ListableBeanFactory,// 並不具備ConfigurableBeanFactory.addBeanPostProcessor(...)的能力。
        // 對ApplicationContext來說,配置檔案中配置的BeanPostProcessor就是所有的了
        String[] beanNames = beanFactory.getBeanDefinitionNames(BeanPostProcessor.class);
        if (beanNames.length > 0) {
            for (String beanName : beanNames) {
                // 例項化所有BeanPostProcessor
                BeanPostProcessor processor = getBean(beanName,BeanPostProcessor.class);
                // 註冊進beanFactory
                beanFactory.addBeanPostProcessor(processor);
            }
        }
    }
複製程式碼

  注意這裡beanFactory.getBeanDefinitionNames(...)是通過查詢BeanFactory持有的BeanDefinition(s)來判斷,並不會導致任何bean的例項化。換句話說,執行完registerBeanPostProcessors(...),除了BeanFactoryPostProcessorBeanPostProcessor被例項化了之外,還沒有其他bean被例項化。 NOTE: Spring還額外提供了Ordered介面來調整BeanFactoryPostProcessorBeanPostProcessor的呼叫順序,tiny-spring並沒有做這些。

  接下來的initApplicationEventMulticaster()建立了ApplicationEventMulticaster,它被用來實現ApplicationEventPublisher

    @Override
    public void publishEvent(ApplicationEvent event) {
        eventMulticaster.multicastEvent(event);
    }
複製程式碼

現在只要根據事件找到事件對應的監聽器,把它交給ApplicationEventMulticaster管理,一個完整的觀察者模式就實現了。在這裡Observable對應ApplicationEventObserver對應ApplicationListener,事件釋出則由ApplicationEventMulticaster來處理。onRefresh也是一個鉤子方法,預設只是一個空的實現。

   protected void registerApplicationListeners() {
        // 1. 重新註冊所有手動註冊上去的ApplicationListener
        manuallyRegisteredListeners.forEach(eventMulticaster::addApplicationListener);
        // 2. 註冊配置檔案中定義的ApplicationListener
        Collection<Object> listeners = getBeansOfType(ApplicationListener.class,true,false).values();
        for (Object listener : listeners) {
            eventMulticaster.addApplicationListener((ApplicationListener) listener);
        }
    }
複製程式碼

剩下的finishBeanFactoryInitialization(...)實現了ApplicationContext在啟動時一次性初始化所有non lazy initJavaBean的功能:

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.preInstantiateSingletons();
    }
複製程式碼

直接呼叫ConfigurableListableBeanFactory.preInstantiateSingletons(),簡潔明瞭。finishRefresh()意味著重新整理結束,因此它發出了ContextRefreshedEvent事件通知上一步註冊的所有ApplicationListener

    protected void finishRefresh() {
        publishEvent(new ContextRefreshedEvent(this));
    }
複製程式碼

  除了可以重新整理,ApplicationContext還可以在不需要時被關閉:

   @Override
    public void close() {
        System.out.println("正在關閉" + getDisplayName());
        // 銷燬所有快取的singleton bean
        getBeanFactory().destroySingletons();
        // 發出ContextClosedEvent
        publishEvent(new ContextClosedEvent(this));
    }
複製程式碼

ApplicationContext在被關閉時會銷燬所有的singleton bean,併發出ContextClosedEvent通知所有的ApplicationListener

  AbstractRefreshableApplicationContextAbstractApplicationContext的子類,它實現了BeanFactory的建立和重新整理,並將配置檔案的載入留給了子類。

   @Override
    protected ConfigurableListableBeanFactory refreshBeanFactory() {
        // 重新整理時如果BeanFactory已經存在,首先要進行資源的清理
        if (beanFactory != null) {
            beanFactory.destroySingletons();
            beanFactory = null;
        }
        // 清理完畢重新建立BeanFactory並載入配置檔案
        try {
            DefaultListableBeanFactory listableBeanFactory = createBeanFactory();
            loadBeanDefinitions(listableBeanFactory);
            beanFactory = listableBeanFactory;
            return listableBeanFactory;
        } catch (Exception e) {
            throw new ApplicationContextException("重新整理BeanFactory失敗",e);
        }
    }

    /**
     * 建立BeanFactory,可以由子類進一步定製。
     */
    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory();
    }

    /// MARK - Template method

    /**
     * 將載入BeanDefinition的功能交由子類去實現,
     * 子類根據資源的型別,運用不同的載入方式,從而派生出不同的ApplicationContext。
     */
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException;
複製程式碼

AbstractXMLApplicationContext在此基礎之上實現了對配置檔案的載入但遮蔽了它的來源,ClassPathXMLApplicationContext交代了配置檔案的來源,從而成為一個真正可用的ApplicationContext

BeanFactoryPostProcessor示例

  在上一篇中,我們提到PropertyEditorRegistrar,使用它可以向BeanFactory中註冊自定義的PropertyEditor,現在讓我們來實現它。在瞭解了ApplicationContext的實現之後,我們知道在為BeanFactoryPostProcessor執行回撥的時候,所有的BeanDefinition已經載入完畢,但是還沒有任何bean得到了初始化,這是一個非常好的時機去更新BeanDefinitionCustomEditorConfigurer就利用了這個時機,先註冊PropertyEditorBeanFactory中,後續bean的初始化過程就能用上自定義的PropertyEditor

// 命令列輸入 git checkout ninth-stage
    public class CustomEditorConfigurer implements BeanFactoryPostProcessor {
    
        // PropertyEditor註冊器
        private PropertyEditorRegistrar[] propertyEditorRegistrars;
    
        public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
            this.propertyEditorRegistrars = propertyEditorRegistrars;
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            if (propertyEditorRegistrars != null) {
                for (PropertyEditorRegistrar registrar : propertyEditorRegistrars) {
                    beanFactory.addPropertyEditorRegistrar(registrar);
                }
            }
        }
    }
複製程式碼

為了讓PropertyEditorRegistrarBeanFactory產生關聯,我們需要修改一下ConfigurableBeanFactory的定義,增加對PropertyEditorRegistrar的管理:

   /**
     * 新增一個PropertyEditorRegistrar。
     */
    void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
複製程式碼

AbstractBeanFactory中當然也就需要一個集合去儲存PropertyEditorRegistrar:

  // 儲存所有的PropertyEditorRegistrar
    private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new HashSet<>();
    
        @Override
    public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
        propertyEditorRegistrars.add(registrar);
    }
複製程式碼

上一篇中也說到屬性注入最後是交給了BeanWrapper去處理,BeanWrapper因而需要同步BeanFactory中的PropertyEditor。因此我們需要修改一下AbstractAutowireCapableBeanFactory中的同步過程,將與PropertyEditorRegistrar關聯的PropertyEditor也同步過去。

    /**
     * 將BeanFactory持有的PropertyEditor同步到BeanWrapper
     */
    private void registerPropertyEditors(BeanWrapper wrapper) {
        // 1. 註冊PropertyEditorRegistrar中的
        for (PropertyEditorRegistrar registrar : getPropertyEditorRegistrars()) {
            registrar.registerCustomEditors(wrapper);
        }
        // 2. 同步BeanFactory持有的
        Map<Class<?>,PropertyEditor> customEditors = getCustomEditors();
        Set<Class<?>> keys = customEditors.keySet();
        for (Class<?> type : keys) {
            wrapper.registerCustomEditor(type,customEditors.get(type));
        }
    }
複製程式碼

至此一切關節都一打通,test目錄下有一個對CustomEditorConfigurer的使用示例。

結語

  ApplicationContext介紹到這裡就差不多了,我是愛呆毛的小士郎,歡迎交流~