一、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 注解的參數指定。