本篇部落格主要參考:Spring官網閱讀(四)BeanDefinition(上)
引入主題
為什麼要讀Spring原始碼,有的人為了學習Spring中的先進思想,也有的人是為了更好的理解設計模式,當然也有很大一部分小夥伴是為了應付面試,Spring Bean的生命週期啦,Spring AOP的原理啦,Spring IoC的原理啦,應付面試,看幾篇部落格,對照著看看原始碼,應該就沒什麼問題了,但是如果想真正的玩懂Spring,需要花的時間真的很多,需要你沉下心,從最基礎的看起,今天我們就來看看Spring中的基礎——BeanDefinition。
什麼是BeanDefinition
Spring官網中有詳細的說明,我們來翻譯下:
SpringIoc容器管理一個Bean或多個Bean,這些Bean通過我們提供給容器的配置後設資料被建立出來(例如,在xml中
在容器中,這些Bean的定義用BeanDefinition物件來表示,包含以下後設資料:
- 全限定類名, 通常是Bean的實際實現類;
- Bean行為配置元素,它們說明Bean在容器中的行為(作用域、生命週期回撥等等);
- Bean執行工作所需要的的其他Bean的引用,這些Bean也稱為協作者或依賴項;
- 其他配置資訊,例如,管理連線池的bean中,限制池的大小或者使用的連線的數量。
Spring官網中對BeanDefinition的解釋還是很詳細的,但是不是那麼通俗易懂,其實BeanDefinition是比較容易解釋的:BeanDefinition就是用來描述一個Bean或者BeanDefinition就是Bean的定義。
建立一個Java Bean,大概是下面這個醬紫:
我們寫的Java檔案,會編譯為Class檔案,執行程式,類載入器會載入Class檔案,放入JVM的方法區,我們就可以愉快的new物件了。
建立一個Spring Bean,大概是下面這個醬紫:
我們寫的Java檔案,會編譯為Class檔案,執行程式,類載入器會載入Class檔案,放入JVM的方法區,這一步還是保持不變(當然這個也沒辦法變。。。)
下面就是Spring的事情了,Spring會解析我們的配置類(配置檔案),假設現在只配置了A,解析後,Spring會把A的BeanDefinition放到一個map中去,隨後,由一個一個的BeanPostProcessor進行加工,最終把經歷了完整的Spring生命週期的Bean放入了singleObjects。
BeanDefinition類圖鳥瞰
大家可以看到,Spring中BeanDefinition的類圖還是相當複雜的,我剛開始讀Spring原始碼的時候,覺得BeanDefinition應該是一個特別簡單的東西,但是後面發覺並不是那麼回事。
下面我將對涉及到的類逐個進行解讀。
AttributeAccessor
AttributeAccessor是一個介面:
/**
* Interface defining a generic contract for attaching and accessing metadata
* to/from arbitrary objects.
*
* @author Rob Harrop
* @since 2.0
*/
public interface AttributeAccessor {
void setAttribute(String name, @Nullable Object value);
Object getAttribute(String name);
Object removeAttribute(String name);
boolean hasAttribute(String name);
String[] attributeNames();
}
我們來看下類上面的註釋:介面定義了通用的方法來儲存或者讀取後設資料。既然是介面,那麼一定會有實現類,我們先把這個放一邊。
BeanMetadataElement
BeanMetadataElement也是一個介面,裡面只定義了一個方法:
/**
* Interface to be implemented by bean metadata elements
* that carry a configuration source object.
*
* @author Juergen Hoeller
* @since 2.0
*/
public interface BeanMetadataElement {
@Nullable
Object getSource();
}
我們還是來看下類上的註釋:介面提供了一個方法來獲取Bean的源物件,這個源物件就是原始檔,怎麼樣,是不是不太好理解,沒關係,我們馬上寫個程式碼來看下:
@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class BookService {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBeanDefinition("bookService").getSource());
}
}
file [D:\cycleinject\target\classes\com\codebear\springcycle\BookService.class]
怎麼樣,現在理解了把。
AttributeAccessorSupport
AttributeAccessorSupport類是一個抽象類,實現了AttributeAccessor介面,這個AttributeAccessor還記得吧,裡面定義了通用的方法來儲存或者讀取後設資料的虛方法,AttributeAccessorSupport便實現了這個虛方法,AttributeAccessorSupport定義了一個map容器,後設資料就被儲存在這個map裡面。
為什麼要有這個map
初次讀Spring原始碼,看到這個map的時候,覺得有點奇怪,後設資料不應該是儲存在BeanDefinition的beanClass、scope、lazyInit這些欄位裡面嗎?這個map不是多次一舉嗎?
後面才知道,Spring是為了方便擴充套件,不然BeanDefinition有新的特性,就要新增欄位,這是其一;其二,如果程式設計師要擴充套件Spring,而BeanDefinition中定義的欄位已經無法滿足擴充套件了呢?
那Spring自己有使用這個map嗎,答案是有的,我們來看下,Spring在這個map中放了什麼資料:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
BeanDefinition appConfig = context.getBeanDefinition("appConfig");
for (String item : appConfig.attributeNames()) {
System.out.println(item + ":" + appConfig.getAttribute(item));
}
}
org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:full
org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass:true
可以看到,Spring在裡面放了兩個item:
- 第一個item儲存著這個配置類是否是一個Full配置類,關於Full配置類,我在先前的部落格有簡單的介紹過:Spring中你可能不知道的事(二)
- 第二個item,從名字上就可以知道和AOP相關。
BeanDefinition
BeanDefinition是一個介面,繼承了AttributeAccessor、BeanMetadataElement,這兩個類上面已經介紹過了。
BeanDefinition定義了很多方法,比如setBeanClassName、getBeanClassName、setScope、getScope、setLazyInit、isLazyInit等等,這些方法一眼就知道是什麼意思了,這裡就不解釋了。
BeanMetadataAttributeAccessor
BeanMetadataAttributeAccessor繼承了AttributeAccessorSupport,對儲存或者讀取後設資料的方法進行了進一步的封裝。
AbstractBeanDefinition
AbstractBeanDefinition是一個抽象類,繼承了BeanMetadataAttributeAccessor,實現了BeanDefinition。
BeanDefinition實現了BeanDefinition定義的大部分虛方法,同時定義了很多常量和預設值。
AbstractBeanDefinition有三個子類,下面我們來看看這三個子類。
ChildBeanDefinition
從Spring2.5開始,ChildBeanDefinition已經不再使用,取而代之的是GenericBeanDefinition。
GenericBeanDefinition
GenericBeanDefinition替代了ChildBeanDefinition,ChildBeanDefinition從字面上,就可以看出有“子BeanDefinition”的意思,難道BeanDefinition還有“父子關係”嗎?當然有。
public class ChildService {
private int id;
private String name;
public ChildService(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ParentService {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ParentService(int id, String name) {
this.id = id;
this.name = name;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
GenericBeanDefinition parentBeanDefinition = new GenericBeanDefinition();
parentBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
parentBeanDefinition.setAttribute("name", "codebear");
parentBeanDefinition.setAbstract(true);
parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(1);
parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("CodeBear");
GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
childBeanDefinition.setParentName("parent");
childBeanDefinition.setBeanClass(ChildService.class);
context.registerBeanDefinition("parent", parentBeanDefinition);
context.registerBeanDefinition("child", childBeanDefinition);
context.refresh();
BeanDefinition child = context.getBeanFactory().getMergedBeanDefinition("child");
for (String s : child.attributeNames()) {
System.out.println(s + ":" + child.getAttribute(s));
}
System.out.println("scope:" + child.getScope());
System.out.println("-------------------");
ChildService service = context.getBean(ChildService.class);
System.out.println(service.getName());
System.out.println(service.getId());
}
執行結果:
name:codebear
scope:singleton
-------------------
CodeBear
1
來分析下程式碼:
- 建立了GenericBeanDefinition物件parentBeanDefinition,設定為了單例模式,設定了Attribute,宣告瞭構造方法的兩個引數值;
- 建立了GenericBeanDefinition物件childBeanDefinition,設定parentName為parent,BeanClass為ChildService;
- 註冊parentBeanDefinition,beanName為parent,childBeanDefinition,beanName為child;
- 重新整理容器;
- 從mergedBeanDefinitions取出了child,mergedBeanDefinitions存放的是合併後的BeanDefinition;
- 列印出child的attribute、scope、構造方法的兩個引數值。
大家可以看到,childBeanDefinition繼承了parentBeanDefinition。
如果沒有父子關係,單獨作為BeanDefinition,也可以用GenericBeanDefinition來表示:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(AuthorService.class);
genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
context.registerBeanDefinition("authorService", genericBeanDefinition);
context.refresh();
BeanDefinition mergedBeanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");
BeanDefinition beanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");
System.out.println(mergedBeanDefinition);
System.out.println(beanDefinition);
執行結果:
Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
可以看到,當沒有父子關係,beanDefinition依舊會被儲存在mergedBeanDefinitions中,只是儲存的內容和beanDefinitions中所儲存的內容是一模一樣的。
GenericBeanDefinition總結
GenericBeanDefinition替代了低版本Spring的ChildBeanDefinition,GenericBeanDefinition比ChildBeanDefinition、RootBeanDefinition更加靈活,既可以單獨作為BeanDefinition,也可以作為父BeanDefinition,還可以作為子GenericBeanDefinition。
RootBeanDefinition
在介紹GenericBeanDefinition的時候,寫了兩段程式碼。
給第一個程式碼打上斷點,觀察下mergedBeanDefinitions,會發現parentBeanDefinition和
childBeanDefinition在mergedBeanDefinitions都變為了RootBeanDefinition:
給第二個程式碼打上斷點,也觀察下mergedBeanDefinitions,會發現authorService在mergedBeanDefinitions也變為了RootBeanDefinition:
可以看到在mergedBeanDefinitions存放的都是RootBeanDefinition。
RootBeanDefinition也可以用來充當父BeanDefinition,就像下面的醬紫:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
genericBeanDefinition.setBeanClass(ParentService.class);
genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
context.registerBeanDefinition("parent", genericBeanDefinition);
GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
rootBeanDefinition.setBeanClass(ChildService.class);
rootBeanDefinition.setParentName("parent");
context.refresh();
}
但是RootBeanDefinition不可以充當子BeanDefinition:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
genericBeanDefinition.setBeanClass(ParentService.class);
genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
context.registerBeanDefinition("parent", genericBeanDefinition);
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(ChildService.class);
rootBeanDefinition.setParentName("parent");
context.refresh();
}
執行結果:
Exception in thread "main" java.lang.IllegalArgumentException: Root bean cannot be changed into a child bean with parent reference
at org.springframework.beans.factory.support.RootBeanDefinition.setParentName(RootBeanDefinition.java:260)
at com.codebear.springcycle.Main.main(Main.java:20)
丟擲了異常。
查詢原始碼:
@Override
public void setParentName(@Nullable String parentName) {
if (parentName != null) {
throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
}
}
發現呼叫RootBeanDefinition的setParentName方法,直接丟擲了異常。
RootBeanDefinition總結
RootBeanDefinition可以作為其他BeanDefinition的父BeanDefinition,也可以單獨作為BeanDefinition,但是不能作為其他BeanDefinition的子BeanDefinition,在mergedBeanDefinitions儲存的都是RootBeanDefinition。
ScannedGenericBeanDefinition
@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBeanDefinition("authorService").getClass());
}
}
執行結果:
class org.springframework.context.annotation.ScannedGenericBeanDefinition
通過註解掃描出來的Bean的BeanDefinition用ScannedGenericBeanDefinition來表示。
AnnotatedGenericBeanDefinition
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBeanDefinition("appConfig").getClass());
}
執行結果:
class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition
配置類的BeanDefinition用AnnotatedGenericBeanDefinition來表示。
ConfigurationClassBeanDefinition
public class AuthorService {
}
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public AuthorService authorService() {
return null;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBeanDefinition("authorService").getClass());
}
執行結果:
class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition
用@Bean宣告的Bean的BeanDefinition用ConfigurationClassBeanDefinition來表示。
是不是完全沒想到,一個BeanDefinition可以牽涉到這麼多的內容,這些內容說沒用,確實沒什麼用;說有用,也有用。不明白這些內容,閱讀Spring原始碼會比較懵逼,為什麼會有那麼多的BeanDefinition。這個時候,你就會卡殼,拼命的想弄懂這些BeanDefinition都是用來幹嘛的,但是網上關於BeanDefinition的部落格不算太多,比較好的部落格就更少了,希望此篇文章可以填充這塊空白。