就是要你懂Spring-IOC

Google愛喝茶發表於2018-08-14

為什麼引入IOC?

class Programer {
    Computer computer = new Mac2015();
    private void work() {
        computer.help();
    }
}
複製程式碼

此時有一個問題就是computer和programer耦合在一起,這個programer不具備擴充套件性(它只會用mac2015),如果此時公司換了一批電腦Mac2016,那麼需要重新建立一個新的程式設計師類,這顯然是不合理的。
從設計的角度來講,類本身就是定義的一個模板亦或是一種抽象,如果抽象和具體的實現繫結或者耦合在一起,那麼就不是純粹的抽象了。 這也違背了設計模式中的don't call me法則。所以這個時候要把computer和programer解耦,解耦的方法很簡單。 computer的具體實現由呼叫方指定就ok,而不是在類內部自行指定。那麼類就需要暴露一些介面供外界實現注入功能。
常見的方法有三種

  • 建構函式注入
  • set注入
  • 介面注入

這就是IOC的基本思路,而對於我們web開發來講一般不存在呼叫方(其實也有不過在容器層面),所以如何保證類中屬性的動態注入,這個就要由Spring登場了
接下來,選擇何種注入方式以及需要注入哪些屬性是怎麼通知到Spring的呢? 常見的方法有兩種

  • 註解
  • 配置檔案(xml/properties)

這裡多提一句,bean的例項化方式可以是普通的構造器構造也可以是工廠方法構造 下面這個是通過靜態工廠注入的方式

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
複製程式碼

class指向的是包含工廠方法的類,並非工廠方法返回的類 下面這個是通過普通工廠注入的方式

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
複製程式碼

IOC容器分類

  • BeanFactory

    預設lazy-load,所以啟動較快 常用的一個實現類是DefaultListableBeanFactory,該類還實現了BeanDefinitionRegistry介面,該介面擔當Bean註冊管理的角色 registerBeanDefinition#BeanDefinitionRegistry bean會以beanDefinition的形式註冊在BeanDefinitionRegistry中 BeanFactory介面中只定義了一些關於bean的查詢方法,而真正的工作需要其子類實現的其他介面定義~

  • ApplicationContext

    構建於BeanFactory基礎之上,非lazy,啟動時間較長,除了BeanFactory的基礎功能還提供了一些額外的功能(事件釋出、國際化資訊支援等)

手動與自動

  • 基於配置檔案的注入 我們稱他為手動

    <bean> <name="propertyName" ref="otherBean">

  • 基於註解的注入 我們稱他為全自動

    @Conponet/@Autowire/@Resource + <componet-scan>

  • 還有註解+配置的半自動

    <bean> + @autowire/@resource

關於xml中的bean

一般最頂層是beans標籤 beans標籤有幾個獨特的屬性,對包含的所有bean生效

  • default-lazy-init

    所有bean是否懶載入

  • default-autowire

    所有bean內部注入的方式no(預設)/byName/byType/constrctor/autodetect
    這裡byName/byType ≈ @resource/@autowire

  • default-dependency-check

    是否進行依賴檢查 none(預設)

  • default-init-method

    所有bean的初始化方法比如init

  • default-destroy-method

    所有bean的銷燬方法比如destroy

基本上beans的所有屬性都可以對應到bean中,這裡提一下bean的scope屬性 關於Singleton和Prototype singleton型別的bean的存活時間和容器一樣長 prototype型別的bean的生命週期不由容器維護,容器不再擁有當前物件的引用,任由其自生自滅

想起當初解決的一個問題,持有物件的引用(new/reflect),但是這個物件未進行注入,如何利用Spring對其進行注入?

     applicationContext.getAutowireCapableBeanFactory().
     autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_NO, true);

複製程式碼

Bean的生命週期

先放結論,後面有具體的程式碼論證(圖文結合效果更佳)

就是要你懂Spring-IOC
實踐是檢驗真理的唯一標準,舉個例子

public class Car implements BeanFactoryAware, BeanNameAware,
        InitializingBean, DisposableBean {
    private String carName;
    private BeanFactory beanFactory;
    private String beanName;

    public Car() {
        System.out.println("bean的建構函式");
    }

    public void setCarName(String carName) {
        System.out.println("屬性注入");
        this.carName = carName;
    }

    // 這是BeanFactoryAware介面方法
    public void setBeanFactory(BeanFactory arg0) throws BeansException {
        System.out
                .println("BeanFactoryAware.setBeanFactory()");
        this.beanFactory = arg0;
    }

    // 這是BeanNameAware介面方法
    public void setBeanName(String arg0) {
        System.out.println("BeanNameAware.setBeanName()");
        this.beanName = arg0;
    }

    // 這是InitializingBean介面方法
    public void afterPropertiesSet() throws Exception {
        System.out
                .println("InitializingBean.afterPropertiesSet()");
    }

    // 這是DiposibleBean介面方法
    public void destroy() throws Exception {
        System.out.println("DiposibleBean.destory()");
    }

    // 通過<bean>的init-method屬性指定的初始化方法
    public void myInit() {
        System.out.println("<bean>的init-method屬性指定的初始化方法");
    }

    // 通過<bean>的destroy-method屬性指定的初始化方法
    public void myDestory() {
        System.out.println("<bean>的destroy-method屬性指定的初始化方法");
    }
}
複製程式碼
public class MyBeanPostProcessor implements BeanPostProcessor {

    public MyBeanPostProcessor() {
        super();
        System.out.println("BeanPostProcessor建構函式");
    }

    public Object postProcessAfterInitialization(Object arg0, String arg1)
            throws BeansException {
        if (arg1.equals("car")) {
            System.out
                    .println("BeanPostProcessor.postProcessAfterInitialization()");
        }
        return arg0;
    }

    public Object postProcessBeforeInitialization(Object arg0, String arg1)
            throws BeansException {
        if (arg1.equals("car")) {
            System.out
                    .println("BeanPostProcessor.postProcessBeforeInitialization()");
        }
        return arg0;
    }
}
複製程式碼
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    public MyBeanFactoryPostProcessor() {
        super();
        System.out.println("BeanFactoryPostProcessor建構函式");
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0)
            throws BeansException {
        System.out.println("BeanFactoryPostProcessor.postProcessBeanFactory()");
        System.out.println("我現在可以修改beanDefination但是我這裡就不修改了");
    }
}
複製程式碼
public class MyInstantiationAwareBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter {

    public MyInstantiationAwareBeanPostProcessor() {
        super();
        System.out
                .println("InstantiationAwareBeanPostProcessorAdapter建構函式");
    }

    // 介面方法、例項化Bean之前呼叫
    @Override
    public Object postProcessBeforeInstantiation(Class beanClass,
                                                 String beanName) throws BeansException {
        if(beanName.equals("car")) {
            System.out
                    .println("InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()");
        }
        return null;
    }

    // 介面方法、例項化Bean之後呼叫
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if(beanName.equals("car")) {
            System.out
                    .println("InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()");
        }
        return true;
    }
    
    // 介面方法、設定某個屬性時呼叫
    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs,
                                                    PropertyDescriptor[] pds, Object bean, String beanName)
            throws BeansException {
        if(beanName.equals("car")) {
            System.out
                    .println("InstantiationAwareBeanPostProcessor.postProcessPropertyValues()");
        }
        return pvs;
    }
}
複製程式碼
<bean id="beanPostProcessor" class="test.MyBeanPostProcessor"/>
<bean id="instantiationAwareBeanPostProcessor" class="test.MyInstantiationAwareBeanPostProcessor"/>
<bean id="beanFactoryPostProcessor" class="test.MyBeanFactoryPostProcessor"/>
<bean id="car" class="test.Car" init-method="myInit"
destroy-method="myDestory" scope="singleton" p:carName="BMW"/>
複製程式碼

下面來見證下整個流程

public static void main(String[] args) {
        System.out.println("開始啟動容器");
        ApplicationContext factory = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        System.out.println("容器初始化成功");
        Car car = factory.getBean("car",Car.class);
        System.out.println("開始關閉容器");
        ((ClassPathXmlApplicationContext)factory).registerShutdownHook();
    }
複製程式碼

最終的console輸出為:

開始啟動容器
BeanFactoryPostProcessor建構函式
BeanFactoryPostProcessor.postProcessBeanFactory()
我現在可以修改beanDefination但是我這裡就不修改了
InstantiationAwareBeanPostProcessorAdapter建構函式
BeanPostProcessor建構函式
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
bean的建構函式
InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
InstantiationAwareBeanPostProcessor.postProcessPropertyValues()
屬性注入
BeanNameAware.setBeanName()
BeanFactoryAware.setBeanFactory()
BeanPostProcessor.postProcessBeforeInitialization()
InitializingBean.afterPropertiesSet()
<bean>的init-method屬性指定的初始化方法
BeanPostProcessor.postProcessAfterInitialization()
容器初始化成功
開始關閉容器
DiposibleBean.destory()
<bean>的destroy-method屬性指定的初始化方法
複製程式碼

1)容器啟動階段
容器需要依賴某些工具類(BeanDefinitionReader)對載入的Configuration MetaData進行解析和分析,並將分析後的資訊編組為相應的BeanDefinition,最後把這些儲存了bean定義必 要資訊的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器啟動工作就完成了。
BeanDefinitionReader
1)用來和配置檔案打交道
2)負責從對應的配置檔案中讀取內容並將bean對映到BeanDefinition
3)然後將BeanDefinition註冊到BeanDefinitionRegistry中
eg:PropertiesBeanDefinitionReader、XmlBeanDefinitionReader

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanDefinitionRegistry);
reader.loadBeanDefinitions("classpath:xxx");
return (BeanFactory) beanDefinitionRegistry;
複製程式碼

唯一能插手容器啟動階段的大佬-----BeanFactoryPostProcessor 在容器例項化物件之前,對註冊到容器的BeanDefinition資訊進行修改
下面是幾個比較典型的BeanFactoryPostProcessor

  • PropertyPlaceholderConfigurer

在例項化bean前,將bean配置中的佔位符替換為實際值

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
複製程式碼
# jdbc.properties
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
複製程式碼

在執行時,佔位符內的內容會被替換為屬性檔案中的匹配的value,這個工作就是由PlaceholderConfiguer完成的,不難看出這個類應該實現了BeanFactoryPostProcessor

  • CustomEditorConfigurer

即容器從XML格式的檔案中讀取的都是字串形式,最終應用程式卻是由各種型別的物件所構成。 要想完成這種由字串到具體物件的轉換(不管這個轉換工作最終由誰來做),都需要這種轉換規則 相關的資訊,而CustomEditorConfigurer就是幫助我們傳達類似資訊的。
如下我們需要將String型別的2018/07/27轉為date

<bean id="dateFoo" class="...DateFoo">
    <property name="date">
        <value>2018/07/27</value>
    </property>
</bean>
複製程式碼

使用者可以自定義CustomEditorConfigurer,來指定string到特定型別轉換到邏輯

  • BeanFactoryPostProcessor的實際應用例子

之前專案中使用JUNIT進行單元測試,但是每次單元測試都要載入所有的bean,專案規模如果較大,那麼單元測試啟動需要很長時間,為了解決這個問題開發了一個簡單的BeanFactoryPostProcessor,原理很簡單就是將所有的bean都設定為懶載入模式,單元測試中用到哪個例項化哪個

public class LazyBeanFactoryProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory fac = (DefaultListableBeanFactory) beanFactory;
        Map<String, AbstractBeanDefinition> map = (Map<String, AbstractBeanDefinition>) ReflectionTestUtils.getField(fac, "beanDefinitionMap");
        for (Map.Entry<String, AbstractBeanDefinition> entry : map.entrySet()) {
            entry.getValue().setLazyInit(true);
        }
    }
}
複製程式碼

2)Bean例項化階段 1.InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(Class beanClass,String beanName) 在執行Bean的構造器之前,如果有InstantiationAwareBeanPostProcessor那麼會先執行它的postProcessBeforeInstantiation()如果此時返回的bean不為null,那麼不會再繼續執行後續的Bean流程,只執行postProcessAfterInitialization(),造成了"短路" 有一點需要注意的在提到InstantiationAwareBeanPostProcessor的方法的時候最好指定入參,因為它有好幾個相同名字入參不同的方法,容易發生混淆

2.對bean進行例項化 容器採用策略模式來決定採用何種方式初始化bean例項(反射 or CGLIB預設) 如果配置的bean有look-up or replace此時需要使用CGLIB建立代理類

3.InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation(Class beanClass,String beanName)

4.InstantiationAwareBeanPostProcessor.postProcessPropertiesValues(PropertyValues pvs,PropertyDescriptor[] pds, Object bean, String beanName)

5.對bean進行依賴注入 例項化後的產物是BeanWrapper例項,它是對bean的包裹,接下來對這個包裹設定屬性值 Ps藉助Wrapper可以用統一的方式對物件屬性進行注入,BeanWrapper會使用PropertyEditor進行屬性的型別轉換

6.檢查是否實現Aware介面(BeanFactory獨有) BeanNameAware:將BeanName設定到當前例項中 BeanClassLoaderAware:將載入當前bean的ClassLoader設定到當前例項中 BeanFactoryAware:將BeanFactory設定到當前例項中

7.BeanPostProcessor.postProcessBeforeInitialization 很多ApplicationContext獨有的Aware介面也是通過BeanPostPorcessor實現屬性注入的

public Object postProcessBeforeInitialization(Object bean, String beanName) throws  BeansException {
 if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher 
(this.applicationContext); }
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
   ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); }
    return bean;
}
複製程式碼

8.InititalizingBean.afterPropertiesSet()

9.init-method

10.BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName)

11.容器初始化成功,正常執行業務邏輯

12.DisposableBean.destroy()

13.destroy-method

就是要你懂Spring-IOC

迴圈依賴

Spring對於單例的bean之間的迴圈依賴會進行處理(Set注入),換言之對於單例構造器注入以及prototype型別的bean無法處理迴圈依賴。假設A依賴B,B又依賴於A,在例項化A的時候會將半成品的A放到快取中,再去例項化所依賴的B,而在例項化B過程中又需要去例項化A,此時直接將半成品的A填充到B中,就完成了迴圈依賴Bean的例項化。

關於BeanPostProcessor

  • BeanPostProcessor處理容器內所有符合條件的BeanDefinition
    Ps這裡的符合條件與否在覆寫的方法中自行判斷if(beanName==xxx)
  • A bean post-processor typically checks for callback interfaces or may wrap a bean with a proxy. Some Spring AOP infrastructure classes are implemented as bean post-processors in order to provide proxy-wrapping logic.
    AOP就是通過BeanPostProcessor實現偷樑換柱的
  • Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessors and beans that they reference directly are instantiated on startup, as part of the special startup phase of the ApplicationContext
    實現該介面的bean是很特殊的,它以及它所依賴的bean需要優先啟動,可以將其視為容器啟動的一個階段。而且實現了該介面的bean(或者被依賴的beans)無法對其進行動態代理,因為動態代理本身就是通過BeanPostProcessor實現的,你可以理解為"大力士舉不起自己"

相關文章