Spring原始碼系列(一)--詳細介紹bean元件
阿新 • • 發佈:2020-06-14
# 簡介
spring-bean 元件是 IoC 的核心,我們可以通過`BeanFactory`來獲取所需的物件,物件的例項化、屬性裝配和初始化都可以交給 spring 來管理。
針對 spring-bean 元件,我計劃分成兩篇部落格來講解。本文會詳細介紹這個元件,包括以下內容。下一篇再具體分析它的原始碼。
1. spring-bean 元件的相關概念:例項化、屬性裝配、初始化、bean、beanDefinition、beanFactory。
2. bean 元件的使用:註冊bean、獲取bean、屬性裝配、處理器等。
# 專案環境說明
正文開始前,先介紹下示例程式碼使用的環境等。
## 工程環境
JDK:1.8.0_231
maven:3.6.1
IDE:Spring Tool Suites4 for Eclipse 4.12
Spring:5.2.6.RELEASE
## 依賴引入
除了引入 spring,這裡還額外引入了日誌和單元測試。
```xml
5.2.6.RELEASE
org.springframework
spring-core
${spring.version}
org.springframework
spring-beans
${spring.version}
junit
junit
4.12
test
org.slf4j
slf4j-api
1.7.28
jar
compile
ch.qos.logback
logback-core
1.2.3
jar
ch.qos.logback
logback-classic
1.2.3
jar
```
# 幾個重要概念
## 例項化、屬性裝配和初始化
在 spring-bean 元件的設計中,這三個詞完整、有序地描述了生成一個新物件的整個流程,是非常重要的理論基礎。它們的具體含義如下:
1. 例項化:使用構造方法創建出一個新物件。
2. 屬性裝配:給物件的成員屬性賦值。
3. 初始化:呼叫物件的初始化方法。
下面使用一段程式碼來簡單演示下這個流程。
```java
public class UserService implements IUserService {
private UserDao userDao;
public UserService() {
super();
System.err.println("UserService構造方法被呼叫");
System.err.println(" ||");
System.err.println(" \\/");
}
public void init() {
System.err.println("UserService的init方法被呼叫");
System.err.println(" ||");
System.err.println(" \\/");
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
System.err.println("UserService的屬性裝配中");
System.err.println(" ||");
System.err.println(" \\/");
this.userDao = userDao;
}
}
```
如果我們將這個 bean 交給 spring 管理,獲取 bean 時會在控制檯列印以下內容:
## 什麼是bean
按照官方的說法, bean 是一個由 Spring IoC 容器例項化、組裝和管理的物件。我認為,這種表述是錯誤的,通過`registerSingleton`方式註冊的 bean,它就不是由 Spring IoC 容器例項化、組裝,所以,更準確的表述應該是這樣:
**物件的例項,或者它的描述物件被註冊到了 Spring IoC 容器,並且通過 Spring IoC 容器來獲取得到的物件,就是 bean**。
舉個例子,使用了 Spring 的專案中, Controller 物件、Service 物件、DAO 物件等都屬於 bean。
至於什麼是 IoC 容器,在 spring-bean 元件中,我認為,beanFactory 就屬於 IoC 容器。
## 什麼是beanFactory
從客戶端來看,一個完整的 beanFactory 工廠一般包含以下功能:
1. 註冊別名。對應下圖的`AliasRegistry`介面。
2. 註冊單例物件。對應下圖的`SingletonBeanRegistry`介面。
3. 註冊`BeanDefinition`物件。對應下圖的`BeanDefinitionRegistry`介面。
4. 獲取 bean。對應下圖的`BeanFactory`介面。
在 spring-bean 元件中,`DefaultListableBeanFactory`就是一個完整的 beanFactory 工廠,也可以說是一個 IoC 容器。接下來的例子將直接使用它來作為 beanFactory。
![BeanFactoryUML_01](https://img2020.cnblogs.com/blog/1731892/202006/1731892-20200614181541484-2054058872.png)
至於其他的介面,這裡也補充說明下。`HierarchicalBeanFactory`用於提供父子工廠的支援,`ConfigurableBeanFactory`用於提供配置 beanFactory 的支援,`ListableBeanFactory`用於提供批量獲取 bean 的支援(不包含父工廠的 bean),`AutowireCapableBeanFactory`用於提供例項化、屬性裝配、初始化等一系列管理 bean 生命週期的支援。
## 什麼是beanDefinition
beanDefinaition 是一個描述物件,用來描述 bean 的例項化、初始化等資訊。
在 spring-bean 元件中,beanDefinaition主要包含以下四種:
1. `RootBeanDefinition`:beanFactory 中最終用於 createBean 的 beanDefinaition,**不允許新增 parentName**。在 BeanFactory 中以下三種實現類都會被包裝成`RootBeanDefinition`用於 createBean。
2. `ChildBeanDefinition`:**必須設定 parentName** 的 beanDefinaition。當某個 Bean 的描述物件和另外一個的差不多時,我們可以直接定義一個`ChildBeanDefinition`,並設定它的 parentName 為另外一個的 beanName,這樣就不用重新設定一份。
3. `GenericBeanDefinition`:通用的 beanDefinaition,**可以設定 parentName,也可以不用設定**。
4. `AnnotatedGenericBeanDefinition`:在`GenericBeanDefinition`基礎上增加暴露註解資料的方法。
spring-bean 元件提供了`BeanDefinitionBuilder`用於建立 beanDefinaition,下面的例子會頻繁使用到。
# 使用例子
## 入門--簡單地註冊和獲取bean
下面通過一個入門例子來介紹註冊和獲取 bean 的過程。
```java
@Test
public void testBase() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinition物件
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
// 註冊Bean
beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
// 獲取Bean
IUserService userService = (IUserService)beanFactory.getBean("userService");
System.err.println(userService.get("userId"));
}
```
## 兩種註冊bean的方式
beanFactory 除了支援註冊 beanDefinition,還允許直接註冊 bean 例項,如下。
```java
@Test
public void testRegisterWays() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊Bean-- BeanDefinition方式
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
// 註冊Bean-- Bean例項方式
beanFactory.registerSingleton("userService2", new UserService());
// 獲取Bean
IUserService userService = (IUserService)beanFactory.getBean("userService");
System.err.println(userService.get("userId"));
IUserService userService2 = (IUserService)beanFactory.getBean("userService2");
System.err.println(userService2.get("userId"));
}
```
當然,這種方式僅支援單例 bean 的註冊,多例的就沒辦法了。
## 多種獲取bean的方式
beanFactory 提供了多種方式來獲取 bean 例項,如下。如果同時使用 beanName 和 beanType,獲取到指定 beanName 的 bean 後會進行型別檢查和型別型別,如果都不通過,將會報錯。
```java
@Test
public void testGetBeanWays() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinition物件
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
// 註冊Bean
beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
// 獲取Bean--通過BeanName
IUserService userService = (IUserService)beanFactory.getBean("userService");
System.err.println(userService.get("userId"));
// 獲取Bean--通過BeanType
IUserService userService2 = beanFactory.getBean(IUserService.class);
System.err.println(userService2.get("userId"));
// 獲取Bean--通過BeanName+BeanType的方式
IUserService userService3 = beanFactory.getBean("userService", IUserService.class);
System.err.println(userService3.get("userId"));
}
```
另外,通過 beanName 獲取 bean,這個 beanName 包含以下三種形式:
1. beanName。 如果對應的 bean 是`FactoryBean`,不會返回`FactoryBean`的例項,而是會返回`FactoryBean.getObject`方法的返回結果。
2. alias。通過 alias 對應的 beanName 來獲取 Bean。
3. '&' + factorybeanName。可以返回`FactoryBean`的例項,形式為:一個或多個& + factorybeanName。
```java
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊Bean--註冊的是一個 FactoryBean
UserServiceFactoryBean userServiceFactoryBean = new UserServiceFactoryBean();
beanFactory.registerSingleton("userServiceFactoryBean", userServiceFactoryBean);
// 註冊BeanName的別名
beanFactory.registerAlias("userServiceFactoryBean", "userServiceAlias01");
// 通過BeanName獲取
assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceFactoryBean"));
// 通過別名獲取
assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceAlias01"));
// 通過&+FactoryBeanName的方式
assertEquals(userServiceFactoryBean, beanFactory.getBean("&UserServiceFactoryBean"));
```
## bean衝突的處理
通過 beanType 的方式獲取 bean,如果存在多個同類型的 bean且無法確定最優先的那一個,就會報錯。
```java
@Test
public void testPrimary() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinition物件
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
BeanDefinition rootBeanDefinition2 = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
// 註冊Bean
beanFactory.registerBeanDefinition("UserRegisterBeanDefinition", rootBeanDefinition);
beanFactory.registerBeanDefinition("UserRegisterBeanDefinition2", rootBeanDefinition2);
beanFactory.registerSingleton("UserRegisterSingleton", new User("zzs002", 19));
beanFactory.registerSingleton("UserRegisterSingleton2", new User("zzs002", 18));
// 獲取Bean--通過BeanType
User user = beanFactory.getBean(User.class);
System.err.println(user);
}
```
執行以上方法,將出現 NoUniqueBeanDefinitionException 的異常。
![spring-bean-test01](https://img2020.cnblogs.com/blog/1731892/202006/1731892-20200614181638832-358781947.png)
針對上面的這種問題,可以採取兩種解決方案:
1. 設定`BeanDefinition`物件的 isPrimary = true。這種方式不適用於 registerSingleton 的情況。
2. 為 beanFactory 設定比較器。
其中,1 方案要優先於 2 方案。
```java
@Test
public void testPrimary() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 為BeanFactory設定比較器
beanFactory.setDependencyComparator(new OrderComparator() {
@Override
public Integer getPriority(Object obj) {
return obj.hashCode();
}
});
// 建立BeanDefinition物件
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
// rootBeanDefinition.setPrimary(true); // 設定BeanDefinition物件為isPrimary
BeanDefinition rootBeanDefinition2 = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
// 註冊Bean
beanFactory.registerBeanDefinition("userRegisterBeanDefinition", rootBeanDefinition);
beanFactory.registerBeanDefinition("userRegisterBeanDefinition2", rootBeanDefinition2);
beanFactory.registerSingleton("userRegisterSingleton", new User("zzs002", 19));
beanFactory.registerSingleton("userRegisterSingleton2", new User("zzs002", 18));
// 獲取Bean--通過BeanType
User user = beanFactory.getBean(User.class);
System.err.println(user);
}
```
## 獲取多例物件
預設情況下,我們從 beanFactory 獲取到的 bean 都是單例的,即同一個物件,實際專案中,有時我們需要獲取到多例的 bean,這個時候就可以通過設定 beanDefinition 的 scope 來處理。如下:
```java
@Test
public void testScope() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊Bean-- BeanDefinition方式
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
rootBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
// 獲取Bean--通過BeanType
IUserService userService1 = beanFactory.getBean(IUserService.class);
IUserService userService2 = beanFactory.getBean(IUserService.class);
assertNotEquals(userService1, userService2);
}
```
## 使用TypeConverter獲取自定義型別的物件
當我們同時使用 beanName + beanType 來獲取 bean 時,如果獲取到的 bean 不是指定的型別,這時,不會立即報錯,beanFactory 會嘗試使用`TypeConverter`來強制轉換。而這個型別轉換器我們可以自定義設定,如下。
```java
@Test
public void testTypeConverter() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊型別轉換器
beanFactory.setTypeConverter(new TypeConverterSupport() {
@SuppressWarnings("unchecked")
@Override
public T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
// 將User轉換為UserVO
if(UserVO.class.equals(requiredType) && User.class.isInstance(value)) {
User user = (User)value;
return (T)new UserVO(user);
}
return null;
}
});
BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
beanFactory.registerBeanDefinition("User", rootBeanDefinition);
UserVO bean = beanFactory.getBean("User", UserVO.class);
Assert.assertTrue(UserVO.class.isInstance(bean));
}
```
## 屬性裝配
如果我想在`UserService`中注入`UserDao`,首先,需要在`UserService`中新增定義的 setter/getter 方法,如下:
```java
public class UserService implements IUserService {
private UserDao userDao;
public void save(User user) {
System.err.println("Service save user:" + user);
userDao.save(user);
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
```
在註冊 bean 時需要注意,`UserDao`的 bean 也需要註冊,而且需要更改 userServiceBeanDefinition 的 autowireType 為按 beanType 注入或按 beanName 注入。
```java
@Test
public void testPopulate() {
// 建立BeanFactory物件
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinition物件
AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
// userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);
AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
// 註冊Bean
beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);
// 獲取Bean
IUserService userService = (IUserService)beanFactory.getBean("userService");
userService.save(null);
}
```
執行以上方法,屬性裝配正常。
## bean 例項化、屬性裝配和初始化的處理器
前面講到,我們將 bean 的例項化、屬性裝配和初始化都交給了 spring 處理,然而,有時我們需要在這些節點對 bean 進行自定義的處理,這時就需要用到 beanPostProcessor。
這裡我簡單演示下如何新增處理器,以及處理器的執行時機,至於處理器的具體實現,我就不多擴充套件了。
```java
@Test
public void testPostProcessor() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 新增例項化處理器
beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
if(UserService.class.equals(beanClass)) {
System.err.println("例項化之前的處理。。 --> ");
}
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if(UserService.class.isInstance(bean)) {
System.err.println("例項化之後的處理。。 --> ");
}
return true;
}
});
// 新增屬性裝配處理器
beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
if(UserService.class.isInstance(bean)) {
System.err.println("設定引數前對引數進行調整 --> ");
}
return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
if(UserService.class.isInstance(bean)) {
System.err.println("設定引數前對引數進行檢查依賴關係 --> ");
}
return InstantiationAwareBeanPostProcessor.super.postProcessPropertyValues(pvs, pds, bean, beanName);
}
});
// 新增初始化處理器
beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(UserService.class.isInstance(bean)) {
System.err.println("初始化前,對Bean進行改造。。 --> ");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(UserService.class.isInstance(bean)) {
System.err.println("初始化後,對Bean進行改造。。 --> ");
}
return bean;
}
});
AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
userServiceBeanDefinition.setInitMethodName("init");
userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);
IUserService userService = (IUserService)beanFactory.getBean("userService");
System.err.println(userService.get("userId"));
}
```
執行以上方法,控制檯打印出了整個處理流程。實際開發中,我們可以通過設定處理器來改變改造生成的 bean 。
以上,基本介紹完 spring-bean 元件的使用,下篇部落格再分析原始碼,如果在分析過程中發現有其他特性,也會在這篇部落格的基礎上擴充套件。
> 相關原始碼請移步:[ spring-beans](https://github.com/ZhangZiSheng001/spring-projects/tree/master/spring-beans)
> 本文為原創文章,轉載請附上原文出處連結:[https://www.cnblogs.com/ZhangZiSheng001/p/13126053.html](https://www.cnblogs.com/ZhangZiSheng001/p/1312605