一、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 規範:
- 所有屬性是 private
- 提供預設構造方法
- 提供 getter 和 setter
- 實現 serializable 介面
它和 POJO 其實是一樣的,只不過是遵循 Bean 規範的 POJO 。
Spring 配置檔案
spring 配置檔案主要支援兩種格式:XML 和 Properties 格式
- Properties 配置檔案主要以 key-value 鍵值對的形式存在,不能進行其他操作,使用於簡單的屬性配置
- XML 配置檔案是樹形結構,檔案結構清晰,但是內容比較繁瑣,使用於大型複雜專案
一般來說,Spring 的配置檔案使用 XML 格式。 XML 配置檔案的根元素是
屬性名稱 | 描述 |
---|---|
id | Bean 的唯一識別符號,Spring 容器對 Bean 的配置和管理都通過該屬性完成。id 的值必須以字母開始,可以使用字母、數字、下劃線等符號。 |
name | name 屬性中可以為 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開。Spring 容器可以通過 name 屬性配置和管理容器中的 Bean。 |
class | 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。 |
scope | 用於設定 Bean 例項的作用域,屬性值可以為 singleton(單例)、prototype(原型)、request、session 和 global Session。其預設值是 singleton |
constructor-arg | |
property | |
ref | |
value | |
list | 用於封裝 List 或陣列型別的依賴注入 |
set | 用於封裝 Set 型別的依賴注入 |
map | 用於封裝 Map 型別的依賴注入 |
entry | |
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 的使用
如果在
5.8 Bean 的銷燬
在 Spring 容器關閉時會執行銷燬方法,但是 Spring 容器不會自動去呼叫銷燬方法,而是需要我們主動的呼叫。
如果是 BeanFactory 容器,那麼我們需要主動呼叫 destroySingletons() 方法,通知 BeanFactory 容器去執行相應的銷燬方法;如果是 ApplicationContext 容器,那麼我們需要主動呼叫 registerShutdownHook() 方法,告知 ApplicationContext 容器執行相應的銷燬方法。
5.9 小結
一般情況下,會在 Bean 被初始化後和被銷燬前執行一些相關操作。
Spring 官方提供了 3 種方法實現初始化回撥和銷燬回撥:
- 實現 InitializingBean 和 DisposableBean 介面;
- 在 XML 中配置 init-method 和 destory-method;
- 使用 @PostConstruct 和 @PreDestory 註解。
在一個 Bean 中有多種生命週期回撥方法時,優先順序為:註解 > 介面 > XML。
不建議使用介面和註解,這會讓 pojo 類和 Spring 框架緊耦合。
六、Spring Bean 自動裝配
Bean 的裝配可以理解為依賴關係注入,Bean 的裝配方式也就是 Bean 的依賴注入方式。Spring 容器支援多種裝配 Bean 的方式,如基於 XML 的 Bean 裝配、基於 Annotation 的 Bean 裝配和自動裝配等。基於 XML 的裝配方式主要分為兩種,在 5.2 設定屬性值 中提到的過。
自動裝配就是指 Spring 容器在不使用
名稱 | 說明 |
---|---|
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 註解的引數指定。