先聊聊IOC容器
如果把IOC容器比作一個餐廳,那么BeanDefinition可以看作為原材料,容器中的每一個bean都會有一個對應的BeanDefinition實例,該實例負責保存bean對象的所有必要信息,包括bean對象的class類型、是否是抽象類、構(gòu)造方法和參數(shù)、其它屬性等等。BeanDefinitionRegistry和 BeanFactory就是這份菜譜,BeanDefinitionRegistry抽象出bean的注冊邏輯,而BeanFactory則抽象出了bean的管理邏輯,而各個BeanFactory的實現(xiàn)類就具體承擔了bean的注冊以及管理工作。DefaultListableBeanFactory作為一個比較通用的BeanFactory實現(xiàn),它同時也實現(xiàn)了BeanDefinitionRegistry接口,因此它就承擔了Bean的注冊管理工作。從圖中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry接口則包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注冊管理BeanDefinition的方法。
下面聊聊spring的bean實例化過程,以現(xiàn)在流行的框架springboot為例。
Spring IoC容器的整個工作流程大致可以分為兩個階段:
1.容器啟動階段
容器啟動階段側(cè)重與bean對象管理信息的收集,一般bean有兩種定義方式,一種是定義在配置文件中,一種是通過注解方式定義。前者BeanDefinitionReader會對加載的 ConfigurationMetaData進行解析和分析,并將分析后的信息組裝為相應的BeanDefinition;或者通過ConfigurationClassPostProcessor等BeanFactoryPostProcessor解析注解抽象出BeanDefinition然后注冊到IOC容器。
2.Bean的實例化階段
經(jīng)歷了第一個階段,所有的Bean都通過BeanDefinition的方式注冊到BeanDefinitionRegistry,當某個請求通過容器的getBean方法請求某個對象,或者因為依賴關系容器需要隱式的調(diào)用getBean時,就會觸發(fā)第二階段的活動:容器會首先檢查所請求的對象之前是否已經(jīng)實例化完成。如果沒有,則會根據(jù)注冊的BeanDefinition所提供的信息實例化被請求對象,并為其注入依賴。當該對象裝配完畢后,容器會立即將其返回給請求方法使用。
BeanFactory作為IOC容器的基礎實現(xiàn),默認采用懶加載的模式,即只有訪問容器中的某個實例時,才會對其實例化。ApplicationContext建立在BeanFactory基礎之上,屬于高級容器,還會提供事件監(jiān)聽機制和國際化功能,而且它會在容器啟動時全部完成初始化和依賴注入操作。
上面詳細展示一個bean從BeanDefinition到真正的實例經(jīng)過了哪些過程,了解這個過程有助于你在spring框架內(nèi)清楚執(zhí)行順序前提下更合理利用spring的各種擴展點。
在了解spring的bean生命周期后,來談談springboot提供的各種擴展點
1.BeanFactoryPostProcessor、BeanPostProcessor、DestructionAwareBeanPostProcessor
BeanFactoryPostProcessor主要作用就是在實例化前改變bean的定義,通過order來控制它們的執(zhí)行順序,它對應還有一個子類BeanDefinitionRegistryPostProcessor可以注冊添加自定義bean;BeanPostProcessor主要作用是spring完成bean實例化配置以及在其初始化前后加上自己的處理邏輯,同樣也是通過order來控制它們的執(zhí)行順序;DestructionAwareBeanPostProcessor主要作用是在對象銷毀前加上自己的處理邏輯。
舉例說明
PropertyPlaceholderConfigurer作為一個BeanFactoryPostProcessor,當BeanFactory在第一階段加載完所有配置信息時,BeanFactory中保存的對象的屬性還是以占位符方式存在的,比如 ${jdbc.mysql.url};當PropertyPlaceholderConfigurer作為一個BeanFactory會在第二階段解析占位符完成替換為實際的配置文件字符串功能。
2.bean級的接口
包括:BeanFactoryAware、BeanNameAware、InitializingBean、DiposableBean、FactoryBean這些接口方法都是在實例化對象后調(diào)用的。
這邊主要聊聊FactoryBean存在的意義,factoryBean本質(zhì)是bean,但是它和一般的bean不同之處它的存在會為了裝飾創(chuàng)建bean。當你創(chuàng)建的bean很復雜的時候,或者需要動態(tài)創(chuàng)建的時候,factoryBean就有它的用處了。我在實現(xiàn)自定義衰減隊列時也用到了這個,當時我有一個場景是對于每一個目標衰減的實現(xiàn)類都要創(chuàng)建一個對應的監(jiān)聽消息的處理類,這個時候我用到了FactoryBean,代碼如下
public class HandlerAnnotationFactoryBean implements FactoryBean<DefaultBestNotify> {
@Getter
@Setter
private Class<NotifyHandler> innerClass;
@Getter
@Setter
private ApplicationContext applicationContext;
@Getter
@Setter
private String tagPrefix;
@Override
public DefaultBestNotify getObject() {
return new DefaultBestNotify(innerClass, applicationContext, tagPrefix);
}
@Override
public Class<?> getObjectType() {
return DefaultBestNotify.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
AbstractBeanDefinition bd = BeanDefinitionBuilder
.rootBeanDefinition(HandlerAnnotationFactoryBean.class)
.addPropertyValue("innerClass", beanClass)
.addPropertyValue("applicationContext", applicationContext)
.addPropertyValue("tagPrefix", tagPrefix)
.getBeanDefinition();
registry.registerBeanDefinition(Constants.PREFIX_BEST_BEAN + handler.name(), bd);
還有其他典型的例子
mybatis的MapperFactoryBean獲取mapper接口實例對象
mybatis的SqlSessionFactoryBean通過buildSqlSessionFactory獲取sqlSessionFactory實例對象dubbo的ReferenceBean獲取refrence接口實例對象
spring-boot通過PropertiesConfigurationFactory的bindPropertiesToTarget綁定配置屬性到對象
3.bean自身的方法
包括:<Bean>定義init_method、destrory_method
4. ApplicationListener、ApplicationContextInitializer
ApplicationListener監(jiān)聽事件通知,通過這個擴展點,程序可以實現(xiàn)事件分發(fā)處理模型
@Component
public class DemoListener {
@Async(value = "taskExecutor")
@EventListener
public void process(DemoEvent demoEvent) {
// 發(fā)短信通知
switch (target.getType()) {
case 1:
// 邏輯1
break;
case 2:
//邏輯2
break;
case 3:
//邏輯3
break;
}
}
}
}
ApplicationContextInitializer允許我們對項目初始化時添加一些自定義的實現(xiàn),一般很少使用。