Spring之IOC補充

      上週寫了Spring IOC的簡單使用:Spring 使用之 IOC,這篇將繼續補充說明Spring IOC,順便稍稍總結《Spring 揭祕》中關於Spring IOC的原理。java

      咱們依然經過XML的方式來說解,實際的開發中,常用XML+自動包掃描的方式來告訴Spring 容器對象間的依賴關係。spring

1、XML配置補充 

一、依賴配置補充       

         首先,在上一篇文章中XML配置注入對象時,一、使用了Set注入,即經過<property></property>標籤,說明實例須要被配置的對象。二、經過構造方法的方式注入依賴的對象。分別以下:數據庫

<bean id="company" class="cn.springstudy.vo.Company">
      <property name="employee" ref="employee"></property>
</bean>
複製代碼

<bean id="company" class="cn.springstudy.vo.Company"
    c:employee-ref="employee">
</bean>
複製代碼

那若是咱們懶得指定Company依賴於Employee對象呢??有沒辦法讓容器本身去找依賴關係呢,答案固然是確定的,咱們能夠指定autowire屬性,autowire屬性可取[no | byName | byType | constructor | autodetect]中的值。apache

一、默認爲no,即咱們經過上面的構造方法,或者set去手動指定依賴。bash

二、byName,即Spring本身去找bean標識符(bean的name或id)與定義的set方法中set部分後面的名稱一致的對象注入(這裏須要注意了,實際上不是按實例名稱去查找的對象),如:咱們在Company中定義了employee實例,但若是咱們的set方法名爲 setSpecialEmployee(...){....},那麼Spring容器會去找標識符爲specialEmployee對象注入。app

三、byType,根據實例定義類型,分析其依賴類型。
框架

四、construct 構造器注入,經過類型,不過類型並非實例類型,而是構造器參數類型。
ide

五、autodetect 若是對象擁有默認無參構造方法,使用byType類型,不然使用construct類型。若是構造方法注入綁定後還有其餘屬性沒綁定,容器也會使用byType對剩餘的對象屬性進行自動綁定。post

<bean id="company" class="cn.springstudy.vo.Company" autowire="byType">
</bean>複製代碼

二、繼承

      上節的例子,咱們加上咱們又增長類型的公司,咱們定義科技、電商公司測試

package cn.springstudy.vo;
public class TechnologyCompany  extends Company{
}
複製代碼

package cn.springstudy.vo;

public class ECommerceCompany extends  Company{
}複製代碼

那咱們是否是配分別配置他們的都依賴於僱員類??以下,固然,這樣配置並無問題。有沒有更好的的方法??

<bean id="technologyCompany" class="cn.springstudy.vo.TechnologyCompany">
    <property name="employee" ref="employee"></property>
</bean>
<bean id="eCommerceCompany" class="cn.springstudy.vo.ECommerceCompany">
    <property name="employee" ref="employee"></property>
</bean>複製代碼

哈哈哈哈,固然是有的啦,就是指定他們分別繼承的類,就不用本身去指定依賴於Emplouee類了

<bean id="company" class="cn.springstudy.vo.Company">
    <property name="employee" ref="employee"></property>
</bean>
<bean id="technologyCompany" class="cn.springstudy.vo.TechnologyCompany" parent="company">
</bean>
<bean id="eCommerceCompany" class="cn.springstudy.vo.ECommerceCompany" parent="company">
</bean>複製代碼

三、 scrop中的prototype陷阱 

上篇文章說過,Spring中管理的對象默認是單例的,咱們能夠指定做用域讓對象是其餘模式的,prototype就是原型模式,默認每次注入都會建立一個對象。

如今咱們將以上公司、僱員場景作下改吧,以前咱們每次讓公司提供服務,咱們調公司對象的supportService()方法都是同一個僱員來提供服務,如今咱們假設每一個僱員提供服務沒那麼快,那麼當我再調 supportService(),咱們但願是新的僱員來給咱們提供服務。  

首先,咱們先改造下Employee對象,讓咱們測試更方便,在work方法中把如今執行Employee對象this打印出來。

package cn.springstudy.vo;
import org.springframework.stereotype.Component;
public class Employee {
    public void work(){
        System.out.println(this+":Employ start to Work:");
    };
}
複製代碼

package cn.springstudy.vo;

public class Company {
    private Employee employee;

    public void  supportService(){
        employee.work();
    }

    public Employee getEmployee() {
        return employee;
    }

    public void setEmployee(Employee employee) {
        this.employee = employee;
    }
}複製代碼

如今重點來了................,咱們把Employee對象做用域指定爲原型

<bean name="employee" class="cn.springstudy.vo.Employee"  scope="prototype"></bean>
<bean id="company" class="cn.springstudy.vo.Company">
    <property name="employee" ref="employee"></property>
</bean>複製代碼

測試起來............

package cn.springstudy.spring;

import cn.springstudy.vo.Company;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringStudy {
    public static void main(String arg[]){
        //方式一
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Company company = (Company) classPathXmlApplicationContext.getBean("company");
        company.supportService();
        company.supportService();
    }
}複製代碼

結果..............,什麼狀況??爲咱們提供服務的仍是同一個僱員。。。


好像不太符合咱們想要的結果,由於建立Company對象的時候是單首創建了Employee對象,以後set到Company中,後面咱們每次拿到的Employee對象都是一開始set進去的,因此調supportService方法時都是同一個僱員對象執行。有幾下幾個方法進行改造

其1、方法注入

將Company類中work方法稍稍作變更

package cn.springstudy.vo;

public class Company {

    private Employee employee;

    public void  supportService(){
        //這裏不用employee,而是使用getEmployee()方法
        getEmployee().work();
    }
    ................
}複製代碼

改配置爲

<bean id="company" class="cn.springstudy.vo.Company">
    <!--方法注入,調用getEmployee()方法的時候注入一個employee對象-->
    <lookup-method name="getEmployee" bean="employee"/>
</bean>複製代碼

再執行上面的測試代碼,每次調company.supportService()都是不一樣的Employee對象爲咱們服務了


方法2、從容器中獲取Employee對象

Company方法實現BeanFactoryAware接口,實現該接口的Bean,容器建立後,會把容器的引用傳給該對象,因而Company類改成如下,由於Employee是原型的,因此咱們每次從容器中獲取都會取得一個新的Employee對象。執行結果同上

package cn.springstudy.vo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class Company implements BeanFactoryAware{

    private Employee employee;
    private  BeanFactory beanFactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    public void  supportService(){
        getEmployee().work();
    }
    public Employee getEmployee() {
        return (Employee) beanFactory.getBean("employee");
    }
    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

}複製代碼

2、Spring IOC原理窺探

Spring 容器實現分爲兩個階段:一、容器啓動階段,二、Bean實例化階段。

一、容器啓動階段

(1)、加載Configuration MataData解析

(2)、分析 將Bean信息保存到BeanDefinition 

(3)、將BeanDefinition 註冊到BeanDefinitionRegistry

(4)、其餘.....................

那咱們是否有機會在容器啓動後對容器作一些操做呢??Spring提供了BeanFactoryPostProcessor 機制,咱們可經過實現BeanFactoryPostProcessor 接口來實現啓動容器後對BeanDefinition(根據配置文件解析獲得的Bean信息)作修改。但咱們不多直接去實現BeanFactoryPostProcessor,更多的是使用Spring提供給咱們的BeanFactoryPostProcessor,常見的有一下兩個:PropertyPlaceholderConfigurer和OverrideConfigurer 。

爲了處理配置文件中的數據類型與真正的業務對象所定義的數據類型轉換,Spring還容許咱們經過CustomEditorConfigurer 來 注 冊 自 定 義 的 PropertyEditor以補助容器中默認的PropertyEditor 

1)、PropertyPlaceholderConfigurer主要是用來加載外部的配置文件,並經過佔位符的方式將Properties文件中的數據寫入對應的BeanDefinition。咱們可在工廠跟目錄下建立一個dataSource.properties文件來存儲數據庫的配置信息,經過 ${name}佔位符的方式將數據注入到對象中

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:datasource.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>複製代碼

2)、PropertyOverrideConfigurer可對容器中配置的任何你想處理的bean定義的property信息進行覆蓋替換。 如1)中咱們忽然以爲jdbc.url想作修改,咱們又不想修改PropertyPlaceholderConfigurer的配置文件,因而咱們能夠建立另外一個配置文件,將jdbc.url改成新的值,再作以下配置

<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"> 
	<property name="location" value="classpath:adjustment.properties"/>
</bean>
複製代碼

3)、參數類型轉換CustomEditorConfigurer

如今咱們想給公司增長一個成員變量,類型爲Date,表示公司建立時間,咱們在XML中配置的是字符串,那麼咱們怎麼讓咱們配置的 2018/11/15自動轉換爲Date類型???

先定義一個轉換類,繼承自PropertyEditorSupport,在這個類中實現轉換

package cn.springstudy.spring;

import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DatePropertyEditor extends PropertyEditorSupport {

    private String datePattern;
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(getDatePattern());
        Date dateValue = null;
        try {
            dateValue = dateTimeFormatter.parse(text);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        setValue(dateValue);
    }

    public String getDatePattern() {
        return datePattern;
    }
    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }
}複製代碼

再寫一個註冊類

package cn.springstudy.spring;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;

import java.beans.PropertyEditor;
import java.util.Date;

public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {

    private PropertyEditor propertyEditor;

    public PropertyEditor getPropertyEditor() {
        return propertyEditor;
    }

    public void setPropertyEditor(PropertyEditor propertyEditor) {
        this.propertyEditor = propertyEditor;
    }

    @Override
    public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) {
        propertyEditorRegistry.registerCustomEditor(Date.class, propertyEditor);
    }
}複製代碼

增長配置

<bean id="datePropertyEditor" class="cn.springstudy.spring.DatePropertyEditor">
    <property name="datePattern">
        <value>yyyy/MM/dd</value>
    </property>
</bean>
<bean id="datePropertyEditorRegistrar" class="cn.springstudy.spring.DatePropertyEditorRegistrar">
    <property name="propertyEditor">
        <ref bean="datePropertyEditor"/>
    </property>
</bean>
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
    <ref bean="datePropertyEditorRegistrar"/>
</list></property>
</bean>複製代碼

如今咱們就能夠給Company加上一個建立時間的實例了。

package cn.springstudy.vo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;

import java.util.Date;

public class Company{

    private Employee employee;
    private Date createDate;

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    ...
}
複製代碼

<bean id="company" class="cn.springstudy.vo.Company">
    <property name="employee" ref="employee"></property>
    <property name="createDate">
        <value>2018/11/18</value>
    </property>
</bean>複製代碼

這樣就能將配置的2018/11/18自動解析爲Date類型。

二、Bean實例化階段

(1)、檢查請求的對象是否已經初始化, 

(2)、沒有則根據BeanDefinitionRegistry中的信息實例化被請求對象, 併爲其注入依賴

(3)、生命週期回調、註冊回調接口

再來看看bean的生命週期

實例化Bean --> 填充屬性(依賴注入)   --> 各類Aware方法調用 

-->   BeanPostProcess的預初始化方法 

--> 調用InitializingBean的afterPropertiesSet方法 

--> 調用自定義的初始化方法 

--> 調用BeanPostProcess的初始化後方法 

-->  -----到這裏Bean可使用--------

--> 調用DisposableBean的destroy()方法

--> 調用自定義的銷燬方法  

1)、實例化,默認經過CglibSubclassingInstantiationStrategy來實例化對象,繼承了SimpleInstantiationStrategy的以反射方式實例化對象的功能,而且經過CGLIB 的動態字節碼生成功能,該策略實現類能夠動態生成某個類的子類,進而知足了方法注入所需的對象 實例化需求
 2)、Spring容器會檢查當前對象實例是否實現了一系列的以Aware命名結尾的接口定義。若是是,則將這些Aware接口定義中規定的依賴注入給當前對象實例 

(1)、BeanNameAware。若是Spring容器檢測到當前對象實 例實現了該接口,會將該對象實例的bean定義對應的beanName設置到當前對象實例。 

(2)、BeanClassLoaderAware。若是容器檢測到當前對 象實例實現了該接口,會將對應加載當前bean的Classloader注入當前對象實例。

(3)、BeanFactoryAware。若是對象聲明實現了 BeanFactoryAware接口,BeanFactory容器會將自身設置到當前對象實例。

3)、BeanPostProcess

以前的BeanFactoryPostProcess是在容器啓動階段即將結束時,回調,用於咱們對容器中Bean。這裏的BeanPostProcess是在Bean初始化的時候調用。BeanPostProcess接口定義以下

public interface BeanPostProcessor
{
	Object postProcessBeforeInitialization(Object bean, String beanName) throws  BeansException;
	Object postProcessAfterInitialization(Object bean, String beanName) throws  BeansException;
}
複製代碼

<!--配置爲bean便可-->
<beans>
	<bean id="passwordDecodePostProcessor" class="package.name.PasswordDecodePostProcessor"></bean>
</beans>
複製代碼

咱們能夠定義類,實現BeanPostProcessor,使用場景,好比咱們若是將密碼密文配置在配置文件中,就可在BeanPostProcessor中解出來並設置到Bean中。

4)、InitializingBean和init-method 

           要與容器的bean生命週期管理進行交互,能夠實現Spring InitializingBean和DisposableBean接口。容器爲前者調用afterPropertiesSet(),爲後者調用destroy()以容許bean在初始化和銷燬​​bean時執行某些操做----------------來自官網

可見InitializingBean的方法是在bean初始化後生命週期的的回調,InitializingBean接口定義以下

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}
複製代碼

使用:咱們的bean直接實現initializingBean接口,初始化完bean以後就會調afterPropertiesSet方法

package cn.springstudy.vo;
...
public class Company implements BeanFactoryAware,InitializingBean{

    private Employee employee;
    private Date createDate;

    @Override
    public void afterPropertiesSet() throws Exception {
        //bean初始化,doSomething
    }
    ...
}
複製代碼

另外一種實現方式,以下,效果同上。

package cn.springstudy.vo;
...
public class Company implements BeanFactoryAware{

    private Employee employee;
    private Date createDate;
    public void init(){
    //bean初始化,doSomething
    }
}複製代碼

<!-- 指定init-method屬性,屬性值爲執行的方法名  -->
<bean id="company" class="cn.springstudy.vo.Company" init-method="init">
</bean>
複製代碼

好了,這一節主要仍是對上一節IOC的補充,主要仍是講Spring IOC的一些東西,兩篇下來,可能對IOC還不是面面俱到,但也有個大概的框架。未完,待續...........

相關文章
相關標籤/搜索