天天看點

常用開源架構擴充清單之原理

作者:技術聯盟總壇

一、前言

評價一個架構是否是優秀的,其中必有一點是該架構是否留足了可擴充的接口。我們在實際做項目中很多情況下就是基于某某架構,然後在這個架構留出的擴充接口上進行業務開發,是以很有必要對這些架構留出了哪些擴充點,這些擴充點是幹啥用的有個心知肚明的了解。

二、常用擴充

其中Spring架構擴充調用鍊路圖:

常用開源架構擴充清單之原理

(1).refresh(),是spring解析xml配置檔案,收集bean定義階段。

(2).getBean(),是容器啟動後從IOC擷取bean執行個體的過程.

(3).destory()是IOC容器銷毀階段

2.1 BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

    /*
      在bean注冊到ioc後建立執行個體前修改bean定義或者新增bean注冊,這個是在context的refresh方法調用
     */
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}           

(1)在bean注冊到ioc後建立執行個體前修改bean定義或者新增bean注冊,這個是在context的refresh方法調用.

2.2 ApplicationListener

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * 處理 application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
}           

例如下面捕獲的事件是當IOC容器refresh後觸發的事件,當你需要在IOC容器啟動後做一些事情的時候,你可以實作這個接口做些事情。

public class ServiceProviderRegister implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();

    }
}           

例如webx中WebxComponentsLoader 在rootIOC refresh後會通過onApplicationEvent初始化一些東西

initWebxConfiguration();
        initInternalRequestHandler();
        initRequestContexts();           

2.3 InstantiationAwareBeanPostProcessor

實作該接口并注入IOC容器中,可以對IOC中的bean在執行個體化前和後dosomething。對應(3),(5)。

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {


    //(1)在bean執行個體化前doSomething
    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

    //(2)在bean執行個體化後,初始化前doSomething
    boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

}           

(1)、bean執行個體化前做一些事情,比如擷取類和方法上面的注解,由于執行個體化後的bean一般都被增強過,增強後的bean不能直接擷取注解資訊,要使用AopUtils工具擷取target則擷取注解

(2)、bean執行個體化後做一些事情(目前沒有應用案例)。

2.4 MergedBeanDefinitionPostProcessor

實作該接口并注入IOC容器中,可以對IOC中的bean定義dosomething。

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {
        //對bean定義進行修改
      void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);


}           

例如AutowiredAnnotationBeanPostProcessor類解析autowired注解就是通過該方法:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
            InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
            metadata.checkConfigMembers(beanDefinition);
        }
    }           

2.5 AwareMethods

對應流程圖步驟(6)

public interface BeanNameAware extends Aware {
    /**
      * 擷取bean在IOC容器中名字
     */
    void setBeanName(String name);

}           

(1)、如果你需要擷取自己的bean在IOC容器中的名字則可以實作該接口。

public interface BeanClassLoaderAware extends Aware {

    /**
     * 擷取加載目前bean的類加載器

     */
    void setBeanClassLoader(ClassLoader classLoader);

}           

(2)、如果你想要知道自己的bean是那個類加載加載的則可以實作該接口

public interface BeanFactoryAware extends Aware {

    /**
     * 擷取目前bean所在的IOC容器
     */
    void setBeanFactory(BeanFactory beanFactory) throws BeansException;

}           

(3)、如果你想要知道自己的bean在那個容器,則可實作該接口

2.6 BeanPostProcessor

實作該接口并注入IOC容器中,可以對IOC中的bean在初始化前和後dosomething,對應流程圖(7,(11)

public interface BeanPostProcessor {

    //afterPropertiesSet前執行
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

   //init-method後執行
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}           

2.7、InitializingBean,DisposableBean,Init-method,Destroy-method

對應流程圖步驟(9),(13),(10),(14)

public interface InitializingBean {
    //(1)set屬性設定後,init方法前調用
    void afterPropertiesSet() throws Exception;

}           
public interface DisposableBean {
    //(2)destruction bean的時候調用,在Destroy-method之前調用
    void destroy() throws Exception;

}           

(1)、如果你需要在自己的bean屬性設定後做件事情那麼該bean就可以實作InitializingBean 。中1.2節介紹的SqlSessionFactoryBean。

(2)、如果你需要在自己的bean被銷毀前做一件事情,比如回收資源,那麼可以實作DisposableBean方法,例如SimpleThreadPoolTaskExecutor實作了該方法線上程池銷毀時候決定是不是等目前線程池中線程執行完後在銷毀。

2.8、 ApplicationContextAware

實作該接口并把實作bean注入容器,則可以擷取Spring IOC的容器上下文。對應流程圖(8)

public interface ApplicationContextAware extends Aware {

    //用來擷取Spring IOC容器上下文
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}           

那麼拿到容器上下文可以做啥那?可以看下ApplicationContext這個接口裡面的接口函數就知道了,下面列下常用的功能 (1)、getBean(String name)擷取目前容器中所有的bean,當我們線上程中要使用容器中的bean時候可以使用這種方式。(2)、如果是WebApplicationContext則還可以調用getServletContext擷取應用唯一的servletcontext. https://www.atatech.org/articles/73558 。中1.3節的MapperScannerConfigurer實作了該接口

2.9 BeanNameAutoProxyCreator

該類是個實作類,一般在這個類的postProcessAfterInitialization方法内對類進行代理增強, 該類特殊在于可以指定對IOC容器内的某些名字的類進行增強,并且可以指定增強需要的攔截器,集團内比較牛逼的校驗架構fastvalidator 就是使用這個類對需要校驗的類進行增強,具體配置如下:

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
        //配置需要被增強的IOC中類的名字,
        <property name="beanNames">
            <list>
                <value>validatorService</value>
            </list>
        </property>
       //配置進行增強使用的攔截器
        <property name="interceptorNames">
            <list>
                <value>fastMethodValidationInterceptor</value>
            </list>
        </property>
    </bean>           

2.10 ContextLoaderListener

該listener一般用來啟動Spring容器或者架構的根容器,例如webx架構的WebxContextLoaderListener就是繼承該類,實作了webx架構到tomcat容器的銜接點,而springmvc則通過在listener啟動一個IOC來管理bean。

常用開源架構擴充清單之原理

該類主要方法為:

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

首先ContextLoaderListener一般是在web.xml裡面配置:

常用開源架構擴充清單之原理

其中contextConfigLocation配置的就是要ContextLoaderListener把哪些bean注入ioc容器。那麼配置的全局的contextConfigLocation怎麼在ContextLoaderListener中擷取的?這就需要看下tomcat的代碼時序圖:

常用開源架構擴充清單之原理

從時序圖知道listener中contextInitialized函數的參數event就是ServletContextEvent,而event.getServletContext()就是全應用唯一的ApplicationContext,而ApplicationContext中則儲存了web.xml裡面所有的context-param參數。

那麼使用listener建立spring容器注入bean後,如何從裡面擷取bean那:

ContextLoader.getCurrentWebApplicationContext().getBean(name);           

ContextLoaderListener繼承自ContextLoader,那為何ContextLoader就能拿到容器上下文那:

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

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        ...
        // 這裡建立容器上下文
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }

        // 重新整理容器,加載bean
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        configureAndRefreshWebApplicationContext(cwac, servletContext);

        // 設定容器上下文
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        } else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
    }

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    //設定應用全局唯一的applicationcontext到容器上下文中
    wac.setServletContext(sc);
        //來擷取web.xml裡面的context-param
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);
    //重新整理容器,注入bean到ioc
    wac.refresh();
}

    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }           

延伸一下,其實和listener一樣,servlet的init函數裡面的ServletConfig.ServletContext()擷取的也是應用全局唯一的applicationcontext,filter的init函數裡面的FilterConfig.ServletContext()擷取的也是。

2.11 RequestContextListener

spring中配置bean的作用域時候我們一般配置的都是Singleton,但是有些業務場景則需要三個web作用域,分别為request、session和global session,如果你想讓你Spring容器裡的某個bean擁有web的某種作用域,則除了需要bean級上配置相應的scope屬性,還必須在web.xml裡面配置如下:

<listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>           

那麼這個listener調用邏輯如何那,如下:

常用開源架構擴充清單之原理

bean需要聲明為RequestScope的情況其實蠻多的,比如pvgInfo,在ssovalve裡面根據不同登陸人設定登陸人資訊,然後在業務代碼裡面擷取

繼續閱讀