Spring 學習筆記(2) Spring Bean


一、IoC 容器

IoC 容器是 Spring 的核心,Spring 通過 IoC 容器來管理對象的實例化和初始化(這些對象就是 Spring Bean),以及對象從創建到銷毀的整個生命周期。也就是管理對象和依賴,以及依賴的注入等等。

Spring 提供 2 種不同類型的 IoC 容器:BeanFactory 和 ApplicationContext 容器。

1.1 BeanFactory 容器

BeanFactory 是一個管理 Bean 的工廠,它主要負責初始化各種 Bean, 並調用它們的生命周期方法。BeanFactory 是最簡單的 Bean 容器,它由 org.springframework.beans.factory.BeanFactory 接口定義實現。提供了容器最基本的功能。

目前 BeanFactory 沒多少人用,主要是為了能夠兼容 Spring 集成的第三方框架,所以目前仍然保留了該接口。下面是官網的解釋

The BeanFactory and related interfaces, such as BeanFactoryAware, InitializingBean, DisposableBean, are still present in Spring for the purposes of backward compatibility with the large number of third-party frameworks that integrate with Spring.

Beanfactory 是 org.springframework.beans 的頂級接口。

1.2 ApplicationContext 容器

ApplicationContext 容器幾乎涵蓋所有 BeanFactory 容器的功能,它繼承了 BeanFactory 接口,由org.springframework.context.ApplicationContext 接口定義實現。在 BeanFactory 的基礎上增加了AOP、事務支持等等功能。現在Spring 實際開發中基本上使用的是 ApplicationContext 容器。

ApplicationContext 是 org.springframework.context 的頂級接口。

ApplicationContext 有兩個常用的實現類,分別是 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 。

1.2.1 ClassPathXmlApplicationContext 類

看名字就知道它是從類路徑 ClassPath 中尋找 XML 配置文件,來完成 ApplicationContext 實例化工作:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("configlocation");
//configlocation 是指定 Spring 配置文件(XML)的名稱和位置,比如 Beans.xml

1.2.2 FileSystemXmlApplicationContext

該類是從文件系統中尋找 XML 配置文件:

ApplicationContext applicationContext = new FileSystemXmlApplicationContext("configlocation");
//configlocation 是從非類路徑外中獲取 XML 的名稱和位置,比如 ”F:/workCode/Beans.xml“

它們都是通過 XML 配置文件來加載 Bean 的。

二、 Spring Bean 的定義

看官網定義:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

Bean 是由 Spring IoC 容器管理的對象,容器就能通過反射的形式將容器中准備好的對象注入(這里使用的是反射給屬性賦值)到需求的組件中去,簡單來說,Spring IoC 容器可以看作是一個工廠,Bean 相當於工廠的產品。 Spring 配置文件則告訴容器需要哪些 Bean,以及需要哪種方式來裝配 Bean。

Bean 其實就是一個 Java 對象,它是根據 bean 規范編寫出來的類,並且由容器生成的對象就是一個 bean。

Bean 規范:

  1. 所有屬性是 private
  2. 提供默認構造方法
  3. 提供 getter 和 setter
  4. 實現 serializable 接口

它和 POJO 其實是一樣的,只不過是遵循 Bean 規范的 POJO 。

Spring 配置文件

spring 配置文件主要支持兩種格式:XML 和 Properties 格式

  • Properties 配置文件主要以 key-value 鍵值對的形式存在,不能進行其他操作,使用於簡單的屬性配置
  • XML 配置文件是樹形結構,文件結構清晰,但是內容比較繁瑣,使用於大型復雜項目

一般來說,Spring 的配置文件使用 XML 格式。 XML 配置文件的根元素是 ,該元素下包含多個子元素 。每個 都定義了一個 bean ,並描述了該 Bean 如何被裝配到 Spring 容器中。

元素的常用屬性:

屬性名稱 描述
id Bean 的唯一標識符,Spring 容器對 Bean 的配置和管理都通過該屬性完成。id 的值必須以字母開始,可以使用字母、數字、下划線等符號。
name name 屬性中可以為 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開。Spring 容器可以通過 name 屬性配置和管理容器中的 Bean。
class 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。
scope 用於設定 Bean 實例的作用域,屬性值可以為 singleton(單例)、prototype(原型)、request、session 和 global Session。其默認值是 singleton
constructor-arg 元素的子元素,可以使用此元素傳入構造參數進行實例化。該元素的 index 屬性指定構造參數的序號(從 0 開始),type 屬性指定構造參數的類型
property 元素的子元素,用於調用 Bean 實例中的 setter 方法來屬性賦值,從而完成依賴注入。該元素的 name 屬性用於指定 Bean 實例中相應的屬性名
ref 等元素的子元索,該元素中的 bean 屬性用於指定對某個 Bean 實例的引用
value 等元素的子元素,用於直接指定一個常量值
list 用於封裝 List 或數組類型的依賴注入
set 用於封裝 Set 類型的依賴注入
map 用於封裝 Map 類型的依賴注入
entry 元素的子元素,用於設置一個鍵值對。其 key 屬性指定字符串類型的鍵值,ref 或 value 子元素指定其值
init-method 容器加載 Bean 時調用該方法,類似於 Servlet 中的 init() 方法
destroy-method 容器刪除 Bean 時調用該方法,類似於 Servlet 中的 destroy() 方法。該方法只在 scope=singleton 時有效
lazy-init 懶加載,值為 true,容器在首次請求時才會創建 Bean 實例;值為 false,容器在啟動時創建 Bean 實例。該方法只在 scope=singleton 時有效

三、 Spring Bean 的作用域

Spring 容器在初始化一個 Bean 實例時,同時會指定該實例的作用域。Spring 5 支持 6 種作用域。

3.1 singleton

默認的作用域,單例模式。表示在 Spring 容器中只有一個 Bean 實例,Bean 以單例的方式存在。在容器啟動前就創建好了對象,任何時間獲取都是之前創建好的那個對象。配置方式可以缺省,因為是默認值。<bean class="..."></bean>

3.2 prototype

原型作用域,多實例模式。每次調用 Bean 時都會創建一個新實例。Bean 以多實例的方式存在。容器啟動默認不會創建多實例 bean,每次獲取都會創建一個新的實例 bean 。配置方式為 <bean class="..." scope="prototype"></bean>

3.3 request

在 web 環境下,每次 HTTP 請求都會創建一個 Bean 實例,該作用域只在當前 HTTP Request 內有效。 配置方式為 <bean class="..." scope="request"></bean>

3.4 session

在 web 環境下,每次 HTTP 會話共享一個 Bean 實例,不同的 Session 使用不同的 Bean 實例。該作用域只在當前 HTTP Session 內有效。配置方式為 <bean class="..." scope="session"></bean>

3.5 application

在web 環境下,同一個 web application 共享一個 Bean 實例,該作用域在當前 ServletContext 內有效。

3.6 websocket

在web 環境下,同一個 websocket 共享一個 Bean 實例,該作用域在整個 websocket 中有效。

四、Spring Bean 的注冊方式

Bean 的初始化主要分為兩個過程:Bean 的注冊和 Bean 的實例化。Bean 的注冊主要是指 Spring 通過讀取配置文件獲取各個 bean 的聲明信息,並且對這些信息進行注冊的過程。

4.1 XML 配置文件注冊方式

在 XML 中配置好后進行注冊

<bean id="person" class="org.springframework.beans.Person">
   <property name="id" value="1"/>
   <property name="name" value="Java"/>
</bean>

4.2 Java 注解注冊方式

可以使用 @Component 或 @Configuration + @Bean 來注冊 Bean

@Component
public class Person {
   private Integer id;
   private String name
   // 忽略其他方法
}
@Configuration               //可以理解為 XML 配置文件中的 <beans> 標簽
public class Person {
   @Bean                     //可以理解為 XML 配置文件中的 <bean> 標簽
   public Person  person(){
      return new Person();
   }
   // 忽略其他方法
}

4.3 Java API 注冊方式

使用 BeanDefinitionRegistry.registerBeanDefinition() 方法來注冊 Bean, 代碼如下:

public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
		// 新增 Bean
		registry.registerBeanDefinition("person", personBean);
	}
}

五、Spring Bean 的生命周期

Spring 中的 Bean 的生命周期比較復雜,可以表示為: Bean 的定義 -> Bean 的初始化 -> Bean 的使用 -> Bean 的銷毀

Spring 是根據 Bean 的作用域來管理,對於單實例 singleton 作用域的 Bean, Spring 能夠精確地知道 這個 Bean 的完整生命周期;而對於 prototype 作用域的 Bean, Spring 只負責創建, 當容器創建了 Bean 的實例后,Bean 的實例就交給客戶端管理,Spring 容器將不再跟蹤其生命周期。

首先,從上面幾節中看到,關於 Bean 的定義和初始化中的注冊都在配置文件中或者其他方式提前寫好。下面我們直接從 Bean 初始化中的實例化開始看,一般會有以下幾個過程:

5.1 實例化 Bean

Spring 啟動, 查找並加載需要被 Spring 管理的 Bean , 並實例化 Bean ,實例化就是通過容器生成一個 Bean。實例化 Bean 方式主要有三種:類的無參構造方法、靜態工廠、實例工廠。

5.1.1 無參構造方法創建

在配置文件 XML 中配置 bean, 默認使用了無參構造器創建 bean

<bean id="bean" class="com.spring.demo.Bean"></bean>

然后再通過 getBean() 方法來獲取實例化的 bean

ApplicationContext context = new ClasspathXmlApplicationContext("Bean.xml");
Bean b = (Bean)context.getBean("Bean.xml");

5.1.2 靜態工廠方法創建

同樣也是需要在 XML 中配置 bean :

<bean id="bean" class="com.spring.demo.BeanFactory" factory-method="getBean"></bean>

id 和 class 都定位到某個工廠類,factory-method 表示調用到該類 BeanFactory 下的方法來創建對象,而且這個 getBean 方法必須是靜態方法。

同樣是用 getBean() 方法獲取實例化的 bean ,就不贅余了。

5.1.3 實例工廠方法創建

同樣的,配置 XML 文件

<bean id="beanfactory" class="com.spring.demo.BeanFactory"></bean>
<bean id="bean" factory-bean="beanfactory" factory-method="getbean"></bean>

實例工廠和靜態工廠的區別在於,該實例化方式工廠方法不需要是靜態的,需要先創建對象(在工廠類中新建一個對象),然后再通過對象調用其方法創建 bean。

5.2 設置屬性值(依賴注入)

這個階段需要利用依賴注入完成 Bean 中的所有屬性值的配置注入。容器的注入方法主要有構造方法和 Setter 方法注入。

5.2.1 構造方法注入

注入方式是使用 標簽來實現構造函數的注入,在該標簽中,包含這樣幾種屬性:

  • value: 用於注入基本數據類型以及字符串類型的值
  • ref: 注入已經定義好的 Bean
  • type: 用來指定對應的構造函數
  • index: 若構造函數有多個參數的時候,可以使用index 屬性指定參數的位置,給參數的位置進行排序
<bean id="" class="">
    <constructor-arg index="0" value=""></constructor-arg>
    <constructor-arg index="1" ref=""></constructor-arg>
</bean>

5.2.2 Setter 方法注入

Setter 方法注入的方式是目前 Spring 主流的注入方式,它可以利用 Java Bean 規范所定義的 Setter/Getter 方法來完成注入,可讀性和靈活性都很高,它不需要使用聲明式構造方法,而是使用 Setter 注入直接設置相關的值。

<bean id="person" class="org.springframework.beans.Person">
    <property name="id" value="1"/>
    <property name="name" value="Java"/>
</bean>

在 Spring 實例化 Bean 的過程中,首先會調用默認的構造方法實例化 Bean 的對象,然后通過 Java 的反射機制調用 set 方法進行屬性的注入。因此,setter 注入要求 Bean 的對應類必須滿足一下要求:

  • 必須提供一個默認的無參構造方法
  • 必須為需要注入的屬性提供對應的 setter 方法

5.3 調用 Aware 的相關方法

5.3.1 調用 BeanNameAware 的 setBeanName() 方法

如果 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。

5.3.2 調用 BeanFactoryAware 的 setBeanFactory() 方法

如果 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。

5.3.3 調用 ApplicationContextAware 的 setApplicationContext()方法

如果 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。

5.4 調用 BeanPostProcessor 的預初始化方法

如果 Bean 實現了 BeanPostProcessor 接口,則 Spring 調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。

5.5 調用InitializingBean 的 afterPropertiesSet() 方法和定制的初始化方法

InitializingBean 是一個接口,它有一個 afterPropertiesSet() 方法,在 Bean 初始化時會判斷當前 Bean 是否實現了 InitializingBean,如果實現了則調用 afterPropertiesSet() 方法,進行初始化工作;然后再檢查是否也指定了 init-method,如果指定了則通過反射機制調用指定的 init-method 方法,它的實現源碼如下:

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
        throws Throwable {
    // 判斷當前 Bean 是否實現了 InitializingBean,如果是的話需要調用 afterPropertiesSet()
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) { // 安全模式
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    ((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化
                    return null;
                }, getAccessControlContext());
            } catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        } else {
            ((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化
        }
    }
    // 判斷是否指定了 init-method()
    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // 利用反射機制執行指定方法
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

5.6 調用 BeanPostProcessor 的后初始化方法

如果 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization()。此時,Bean 已經可以被應用系統使用了。

5.7 Bean 的使用

如果在 中指定了該 Bean 的作用域為 singleton,則將該 Bean 放入 Spring IoC 的緩存池中,觸發 Spring 對該 Bean 的生命周期管理;如果在 中指定了該 Bean 的作用域為 prototype,則將該 Bean 交給調用者,調用者管理該 Bean 的生命周期,Spring 不再管理該 Bean。

5.8 Bean 的銷毀

在 Spring 容器關閉時會執行銷毀方法,但是 Spring 容器不會自動去調用銷毀方法,而是需要我們主動的調用。

如果是 BeanFactory 容器,那么我們需要主動調用 destroySingletons() 方法,通知 BeanFactory 容器去執行相應的銷毀方法;如果是 ApplicationContext 容器,那么我們需要主動調用 registerShutdownHook() 方法,告知 ApplicationContext 容器執行相應的銷毀方法。

5.9 小結

一般情況下,會在 Bean 被初始化后和被銷毀前執行一些相關操作。

Spring 官方提供了 3 種方法實現初始化回調和銷毀回調:

  1. 實現 InitializingBean 和 DisposableBean 接口;
  2. 在 XML 中配置 init-method 和 destory-method;
  3. 使用 @PostConstruct 和 @PreDestory 注解。
    在一個 Bean 中有多種生命周期回調方法時,優先級為:注解 > 接口 > XML。

不建議使用接口和注解,這會讓 pojo 類和 Spring 框架緊耦合。

六、Spring Bean 自動裝配

Bean 的裝配可以理解為依賴關系注入,Bean 的裝配方式也就是 Bean 的依賴注入方式。Spring 容器支持多種裝配 Bean 的方式,如基於 XML 的 Bean 裝配、基於 Annotation 的 Bean 裝配和自動裝配等。基於 XML 的裝配方式主要分為兩種,在 5.2 設置屬性值 中提到的過。

自動裝配就是指 Spring 容器在不使用 標簽的情況下,可以自動裝配(autowire)相互協作的 Bean 之間的關聯關系,將一個 Bean 注入其他 Bean 的 Property 中。使用自動裝配需要配置 元素的 autowire 屬性。autowire 屬性有五個值,具體說明如下表所示。

名稱 說明
no 默認值,表示不使用自動裝配,Bean 依賴必須通過 ref 元素定義。
byName 根據 Property 的 name 自動裝配,如果一個 Bean 的 name 和另一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。
byType 根據 Property 的數據類型(Type)自動裝配,如果一個 Bean 的數據類型兼容另一個 Bean 中 Property 的數據類型,則自動裝配。
constructor 類似於 byType,根據構造方法參數的數據類型,進行 byType 模式的自動裝配。
autodetect(3.0版本不支持) 如果 Bean 中有默認的構造方法,則用 constructor 模式,否則用 byType 模式。

5.10 基於注解裝配 Bean

盡管可以使用 XML 來裝配 Bean , 但是如果應用中 Bean 數量過多,會導致 XML 配置文件過於臃腫,對后期維護帶來一定的困難

Java 從 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本開始也提供了對 Annotation 技術的全面支持,我們可以使用注解來配置依賴注入。Spring 默認不使用注解裝配 Bean,因此需要在配置文件中添加 < context:annotation-config >,啟用注解。

Spring 中常用的注解如下。

5.10.1 @Component

可以使用此注解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個組件(Bean),並且可以作用在任何層次。使用時只需將該注解標注在相應類上即可。

5.10.2 @Repository

用於將數據訪問層(DAO層)的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

5.10.3 @Service

通常作用在業務層(Service 層),用於將業務層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

5.10.4 @Controller

通常作用在控制層(如 Struts2 的 Action、SpringMVC 的 Controller),用於將控制層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

5.10.5 @Autowired

可以應用到 Bean 的屬性變量、屬性的 setter 方法、非 setter 方法及構造函數等,配合對應的注解處理器完成 Bean 的自動配置工作。默認按照 Bean 的類型進行裝配。

5.10.6 @Resource

作用與 Autowired 相同,區別在於 @Autowired 默認按照 Bean 類型裝配,而 @Resource 默認按照 Bean 實例名稱進行裝配。

@Resource 中有兩個重要屬性:name 和 type。

Spring 將 name 屬性解析為 Bean 的實例名稱,type 屬性解析為 Bean 的實例類型。如果指定 name 屬性,則按實例名稱進行裝配;如果指定 type 屬性,則按 Bean 類型進行裝配。如果都不指定,則先按 Bean 實例名稱裝配,如果不能匹配,則再按照 Bean 類型進行裝配;如果都無法匹配,則拋出 NoSuchBeanDefinitionException 異常。

5.10.7 @Qualifier

與 @Autowired 注解配合使用,會將默認的按 Bean 類型裝配修改為按 Bean 的實例名稱裝配,Bean 的實例名稱由 @Qualifier 注解的參數指定。

參考文章

  1. The IoC container

  2. spring bean是什么

  3. POJO、JavaBean、Spring Bean 的解釋和區別

  4. 底層源碼分析 Spring 的核心功能和執行流程?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM