其實很簡單——SpringIOC詳解

Spring核心:IoC

IoC容器

Spring 容器是 Spring 框架的核心。容器將創建對象,把它們連接在一起,配置它們,並管理他們的整個生命週期從創建到銷燬。Spring 容器使用依賴注入(DI)來管理組成一個應用程序的組件。這些對象被稱爲 Spring Bean。[1]

Spring主要容器包括 BeanFactoryApplicationContext 兩種,其中ApplicationContext容器是使用最多的容器,它間接繼承了BeanFactory接口,具有BeanFactory容器的全部功能,其繼承關係如下:

圖1:ApplicationContext容器類即成關係

Spring容器的作用是用來存放Bean(需要交給Spring管理的對象)並控制Bean從產生到銷燬的整個生命週期,當應用程序需要用到某一個Bean時,通過容器的 getBean()[2] 方法可以很方便的獲取到這個對象。根據Spring的不同配置方式存在不同的 SpringContext 容器實現,最常用的兩種配置方式,基於XML文件和註解的配置方式都具有自己的具體的容器實現,ClassPathXmlApplicationContext 類對應基於XML文件的配置方式, AnnotationConfigApplicationContext 類對應基於註解的配置方式。以上兩種方式可通過如下代碼獲取Bean對象:

/**
 * 基於XML的配置方式獲取Bean對象
 */
public static void main(String[] args) {
	ApplicationContext context = new ClassPathXmlApplicationContext("/config/beans.xml");
  context.getBean("TestBean");
}

/**
 * 基於註解的配置方式獲取Bean對象
 */
public static void main(String[] args){
  ApplicationContext context = new AnnotationConfigApplicationContext(ConfigBeans.class); 
  context.getBean("TestBean");
}

關於對 IoC控制反轉的理解:

控制不反轉的情況下,編碼過程中如果需要使用一個對象,如 Person 類的對象,則通過 new Person() 調用構造函數的方式直接創建對象,這種方式的缺點是每次都需要創建新的對象,使用n次就會創建n個對象,這種方式在特定場景下[3]會產生不必要的性能損失。

引入了Ioc機制後,所有的對象都會交給IoC容器管理,根據不同的配置,容器來控制對象什麼時候創建新對象,什麼時候銷燬。當需要使用一個 Person 對象時,通過依賴注入的方式添加對應的 Person 對象依賴,而不一定創建新對象。

通過IoC機制,對象的控制者從開發者變成了IoC容器,即實現了控制反轉。

Bean

Bean對象由IoC容器根據配置信息創建,是構成應用程序的基礎。配置信息稱爲元數據,Spring框架規定了一系列可設置的元數據屬性,Spring官方文檔說明了所有可配置的屬性[4]

Property Explained in… DESC
Class Instantiating Beans 必選項,用來設定Bean的類型
Name Naming Beans Bean的唯一標識,xml文件中可以使用id/name
Scope Bean Scopes 設置Bean的作用域
Constructor arguments Dependency Injection 設定構造函數注入的參數
Properties Dependency Injection 設定Bean對象的屬性
Autowiring mode Autowiring Collaborators 設定注入模式
Lazy initialization mode Lazy-initialized Beans 是否懶加載
Initialization method Initialization Callbacks 設定對象初始化方法
Destruction method Destruction Callbacks 設定對象銷燬方法
表1:Bean對象可配置的屬性

Bean和IoC容器的關係

程序啓動時IoC容器會通過配置文件讀取元數據,保存在 BeanFactory 對象的 beanDefinitionMap 屬性中, beanDefinitionMap 是一個 ConcurrentHashMap 類對象,容器根據其中保存的元數據信息,創建相應的Bean對象。創建出來的Bean對象根據配置不同保存到不同的位置,默認狀態的Bean都是單例對象,保存在 singletonObjects 屬性中。下圖是對這個過程的簡單描述:[5]

圖2:IoC容器創建Bean的過程

XML配置文件中定義Bean對象的方式如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
>
		<!-- 創建 Bean 對象,所有屬性使用默認值 -->
    <bean name="Bean" class="cn.sunyog.entity.XMLBean">
      	<!-- 設置對象的屬性值 -->
        <property name="message" value="默認設置的 Bean"/>
    </bean>
</beans>

Bean的類定義如下:

public class XMLBean {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

執行測試代碼:

public static void main(String[] args){
    ApplicationContext context = new ClassPathXmlApplicationContext("/config/beans.xml");
    XMLBean bean = (XMLBean) context.getBean("Bean");//斷點
    System.out.println(bean);
    System.out.println(bean.getMessage());
}

打印結果:

cn.sunyog.entity.XMLBean@45752059
默認設置的 Bean

使用以上代碼,通過debug的方式進入斷點,查看Bean的定義信息和對象保存位置如下:

圖3:元數據信息保存位置

圖4:Bean對象保存位置

Bean的作用域

Spring官方文檔中規定了Bean的作用域所有可選值,包括:singleton、prototype、request、session、application、websocket六種。具體描述如下[6]

Scope Description
singleton 默認作用域,單例模式,整個容器中只有一個Bean對象
prototype 每次使用時創建新對象,Bean使用完成後銷燬
request 每個HTTP請求包含一個自己專屬的Bean對象。只能在web程序中使用。只支持ApplicationContext容器
session 每個 HTTP Session包含一個自己專屬的Bean對象。只能在web程序中使用。只支持ApplicationContext容器
application 針對ServletContext創建的Bean對象。只能在web程序中使用。只支持ApplicationContext容器
websocket 針對WebSocket創建的Bean對象。只能在web程序中使用。只支持ApplicationContext容器
圖2:Bean的作用域說明表
1.XML文件方式配置Bean

類定義代碼:

public class XMLBean{
    private String message;
		//無參構造
  	public XMLBean() {
        System.out.println("構造:"+this);
    }
  
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
		//配置文件中說明的初始化方法
    public void initFunc(){
        System.out.println("Bean 初始化方法執行");
    }
		//配置文件中說明的銷燬方法
    public void destroyFund(){
        System.out.println("Bean 銷燬方法執行");
    }
}

XML文件配置信息:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
>
		<bean id="Singleton-Bean" class="cn.sunyog.bean.XMLBean"
          lazy-init="true" scope="singleton" 
          init-method="initFunc" destroy-method="destroyFund">
        <property name="message" value="XML Bean,懶加載,singleton模式,已設定初始化方法,已設定銷燬方法" />
    </bean>
</beans>

通過 junit 測試框架測試各各個Bean的創建順序

public class XMLBeanFactoryTest {
    private ClassPathXmlApplicationContext context = null;
  
    @Test
    public void singletonBeanTest() {
      	//獲取兩次 單例懶加載bean
        XMLBean bean = (XMLBean) this.context.getBean("Singleton-Bean");
        XMLBean bean2 = (XMLBean) this.context.getBean("Singleton-Bean");
      	//獲取一次默認Bean
        XMLBean bean3 = (XMLBean) this.context.getBean("Bean");
				//打印Bean對象內存地址和message屬性
        printBeanInfo(bean, bean2, bean3);
    }

    private void printBeanInfo(XMLBean... bean) {
        for (XMLBean item : bean) {
            System.out.println(item);
        }
        for (XMLBean item : bean) {
            System.out.println(item.getMessage());
        }
    }

    @Before
    public void doBefore() {
        this.context = new ClassPathXmlApplicationContext("/config/beans.xml");
    }

    @After
    public void doAfter() {
        //銷燬所有bean
        this.context.registerShutdownHook();
    }
}

結果打印及說明:

構造:cn.sunyog.bean.XMLBean@2758fe70
構造:cn.sunyog.bean.XMLBean@2db7a79b
Bean 初始化方法執行
cn.sunyog.bean.XMLBean@2db7a79b
cn.sunyog.bean.XMLBean@2db7a79b
cn.sunyog.bean.XMLBean@2758fe70
XML Bean,懶加載,singleton模式,已設定初始化方法,已設定銷燬方法
XML Bean,懶加載,singleton模式,已設定初始化方法,已設定銷燬方法
默認設置的 Bean


Bean 銷燬方法執行

測試代碼中調用三次 getBean() 方法,但打印結果結果顯示只創建了兩個對象。由於名稱爲 Singleton-Bean 的Bean配置了單例作用域,所以兩次獲取的Bean其實是同一個對象,因此分別只調用了一次 initFunc() 方法和 destroyFunc() 方法。這裏需要注意prototype模式下,Bean對象使用完成後會自動回收,而不是通過IoC容器回收,所以設定的銷燬方法會失效。

另外,測試代碼中先獲取的 Singleton-Bean 對象,後獲取的 Bean 對象,但創建順序是先創建 Bean 對象,後創建 Singleton-Bean 對象,這是由於 Singleton-Bean 設置了lazy-init屬性爲true,只有調用 get Bean() 方法時才創建這個Bean。

2.註解方式配置Bean
@Configuration
public class AnnoBeanConfig{
    @Bean(name = "Anno-Bean",initMethod = "initFunc",destroyMethod = "destroyFunc")
    @Scope("singleton") //作用域
    @Lazy   //懶加載
    public AnnotationBean getAnnotationBean(){
        AnnotationBean bean = new AnnotationBean();
        bean.setMessage("基於註解的配置");
        return bean;
    }
}

測試代碼:

public class AnnoBeanFactoryTest {
    AnnotationConfigApplicationContext context=null;

    @Before
    public void doBefore(){
        this.context=new AnnotationConfigApplicationContext(AnnoBeanConfig.class);
    }

    @After
    public void doAfter(){
        this.context.registerShutdownHook();
    }

    @Test
    public void test(){
        AnnotationBean bean = (AnnotationBean) context.getBean("Anno-Bean");
        this.printBeanInfo(bean);
    }

    private void printBeanInfo(AnnotationBean... bean) {
        for (AnnotationBean item : bean) {
            System.out.println(item);
        }
        for (AnnotationBean item : bean) {
            System.out.println(item.getMessage());
        }
    }
}

打印結果:

構造:cn.sunyog.bean.AnnotationBean@21be3395
註解配置的初始化方法
cn.sunyog.bean.AnnotationBean@21be3395
基於註解的配置


註解配置的銷燬方法

Bean的生命週期

另一種設定Bean的初始化和銷燬回調方法的方式。在定義Bean時,實現 InitializingBean 接口的 afterPropertiesSet() 方法來設定初始化回調;實現 DisposableBean接口的 destroy() 方法來設定銷燬回調方法。

配置代碼:

@Bean(name="Simple-Bean",initMethod = "initFunc",destroyMethod = "destroyFunc")
public SimpleBean getSimpleBean(){
    SimpleBean bean = new SimpleBean();
    bean.setMessage("實現初始化回調和銷燬回調方法");
    return bean;
}

Bean代碼:

public class SimpleBean extends AnnotationBean implements InitializingBean, DisposableBean {
    public void destroy() throws Exception {
        System.out.println("實現 DisposableBean 接口的 銷燬回調 方法");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("實現 InitializingBean 接口的 初始化回調 方法");
    }
}

測試代碼:

@Test
public void testSimpleBean(){
    SimpleBean bean = (SimpleBean) context.getBean("Simple-Bean");
    this.printBeanInfo(bean);
}

打印結果:

構造:cn.sunyog.bean.SimpleBean@16e7dcfd
實現 InitializingBean 接口的 初始化回調 方法
註解配置的初始化方法
cn.sunyog.bean.SimpleBean@16e7dcfd
實現初始化回調和銷燬回調方法


實現 DisposableBean 接口的 銷燬回調 方法
註解配置的銷燬方法

也可以通過配置後置處理器的方式設定初始化前、後處理方法,這種方式需要實現 BeanPostProcessor 接口的 postProcessBeforeInitialization()postProcessAfterInitialization() 方法,後置處理器類代碼如下:

public class PostProcessorBean implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      	//只處理SimpleBean類型
        if (bean instanceof SimpleBean){
            System.out.println(beanName+": 前置處理方法 執行");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof SimpleBean){
            System.out.println(beanName+": 後置處理方法 執行");
        }
        return bean;
    }
}

配置類代碼:

@Bean
public BeanPostProcessor getPostProcessorBean(){
    return new PostProcessorBean();
}

執行 SimpleBean 類的測試代碼,得到打印結果(和SimpleBean的測試打印結果略有不同):

構造:cn.sunyog.bean.SimpleBean@79efed2d
Simple-Bean: 前置處理方法 執行
實現 InitializingBean 接口的 初始化回調 方法
註解配置的初始化方法
Simple-Bean: 後置處理方法 執行
cn.sunyog.bean.SimpleBean@79efed2d
實現初始化回調和銷燬回調方法


實現 DisposableBean 接口的 銷燬回調 方法
註解配置的銷燬方法

Bean的整個生命週期的方法調用順序如下圖,圖中直角方框表示應用程序調用,圓角方框表示容器調用:

graph TD A[容器初始化/調用getBean方法] --> B(執行Bean的構造函數) B -->C{是否配置後置處理器} C --已配置--> D(執行後置處理器的postProcessBeforeInitialization方法) D --> E(執行InitializingBean接口的afterPropertiesSet方法) C --未配置--> E E --> F(執行配置的init-method) F --> Z{是否配置後置處理器} Z --已配置--> G(執行後置處理器的postProcessAfterInitialization方法) G --> H[應用程序使用Bean] Z --未配置--> H H --> I[調用容器關閉方法 如registerShutdownHook] I --> J(執行DisposableBean接口的destroy方法) J --> K(執行配置的destroy-method方法) K --> L[容器銷燬]
圖5:Bean的生命週期方法調用順序

依賴注入DI

依賴注入是Spring框架的核心功能,通過DI來管理Bean之間的依賴關係。有構造函數注入和set方法注入兩種方式實現,構造函數注入使用含參構造函數創建Bean對象,set方法注入調用無參構造函數創建Bean對象或使用無參的工廠方法構造Bean對象,並使用setter方法賦值。

構造方法注入

Bean類代碼:

public class AutowireBean {
    private XMLBean child;

    public AutowireBean(XMLBean child) {
        this.child = child;
        System.out.println("AutowireBean類 含參構造函數執行");
    }

    public AutowireBean() {
        System.out.println("AutowireBean類 無參構造函數執行");
    }

    public XMLBean getChild() {
        return child;
    }

    public void setChild(XMLBean child) {
        this.child = child;
        System.out.println("AutowireBean類 setter函數執行");
    }
}

XML配置文件代碼:

<bean name="Bean" class="cn.sunyog.bean.XMLBean">
    <property name="message" value="默認設置的 Bean"/>
</bean>

<bean id="cons-autowire-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true">
    <constructor-arg name="child" ref="Bean" />
</bean>

測試代碼:

@Test
public void consAutowireBeanTest(){
    AutowireBean bean = (AutowireBean) this.context.getBean("cons-autowire-bean");
    PrintTool.printBeanInfo(bean);
}

/**
 * 打印工具類
 */
public class PrintTool {
    public static void printBeanInfo(XMLBean... bean) {
        for (XMLBean item : bean) {
            System.out.println(item);
        }
        for (XMLBean item : bean) {
            System.out.println(item.getMessage());
        }
    }

    public static void printBeanInfo(AnnotationBean... bean) {
        for (AnnotationBean item : bean) {
            System.out.println(item);
        }
        for (AnnotationBean item : bean) {
            System.out.println(item.getMessage());
        }
    }

    public static void printBeanInfo(AutowireBean... bean){
        for (AutowireBean item : bean) {
            XMLBean child = item.getChild();
            printBeanInfo(child);
        }
    }
}

結果打印:

構造:cn.sunyog.bean.XMLBean@79be0360
AutowireBean類 含參構造函數執行
cn.sunyog.bean.XMLBean@79be0360
默認設置的 Bean

set注入

XML配置代碼:

<bean id="set-autowire-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true">
    <property name="child" ref="Bean" />
</bean>

測試代碼:

@Test
public void setAutowireBeanTest(){
    AutowireBean bean = (AutowireBean) this.context.getBean("set-autowire-bean");
    PrintTool.printBeanInfo(bean);
}

結果打印:

構造:cn.sunyog.bean.XMLBean@79be0360
AutowireBean類 無參構造函數執行
AutowireBean類 setter函數執行
cn.sunyog.bean.XMLBean@79be0360
默認設置的 Bean

Spring的自動裝配

Spring的自動裝配(autowire)在配置文件中設置了bean的autowire屬性後生效,auto-wire屬性有三種模式,分別是byName(按名稱匹配),byType(按類型匹配),constructor(構造函數自動裝配)。其中,前兩種分別按名稱和類型從容器中查找符合條件的Bean,找到後通過set方法入,查找失敗會拋出異常;constructor模式則通過構造方法注入。

注意,在byType模式下要保證這種類型的Bean只進行了一次配置。

三種模式xml配置如下:

<bean name="child" class="cn.sunyog.bean.XMLBean">
    <property name="message" value="默認設置的 Bean"/>
</bean>
<bean id="autowire-name-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="byName" />
<bean id="autowire-type-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="byType" />
<bean id="autowire-cons-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="constructor" />

測試代碼:

@Test
public void autowireTest(){
    AutowireBean bean1 = (AutowireBean) this.context.getBean("autowire-name-bean");
    AutowireBean bean2 = (AutowireBean) this.context.getBean("autowire-type-bean");
    AutowireBean bean3 = (AutowireBean) this.context.getBean("autowire-cons-bean");
}

結果打印:

構造:cn.sunyog.bean.XMLBean@1f36e637
AutowireBean類 無參構造函數執行
AutowireBean類 setter函數執行
AutowireBean類 無參構造函數執行
AutowireBean類 setter函數執行
AutowireBean類 含參構造函數執行

  1. 摘自:https://www.w3cschool.cn/wkspring/f8pc1hae.html ↩︎

  2. getBean()方法是BeanFactory接口的基本方法,包括幾個重載的方法,可以通過名稱、類型、名稱+類型等方式獲取到對應的Bean ↩︎

  3. 如:對象佔用內存特別大,或可以反覆使用的對象等場景。這種場景可通過設計模式中的單例模式解決,Spring框架中也大量使用單例模式 ↩︎

  4. Spring Core官方文檔:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-class ↩︎

  5. 圖片來源:https://www.w3cschool.cn/wkspring/8kei1icc.html ↩︎

  6. 表格來源:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章