Spring 學習筆記(2) Spring Bean

1、IoC 容器

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

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

1.1 BeanFactory 容器

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

目前 BeanFactory 沒多少人用,主要是爲了可以兼容 Spring 集成的第三方框架,因此目前仍然保留了該接口。下面是官網的解釋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.api

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 。websocket

1.2.1 ClassPathXmlApplicationContext 類

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

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 的。

2、 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 時有效

3、 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 中有效。

4、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);
	}
}

5、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 框架緊耦合。

6、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 的核心功能和執行流程?

相關文章
相關標籤/搜索