以下內容基于 Spring6.0.4。
這個話題其實非常龐大,我本來想從 getBean 方法講起,但一想這樣講完估計很多小伙伴就懵了,所以我們還是一步一步來,今天我主要是想和小伙伴們講講 Spring 容器創建 Bean 最最核心的 createBeanInstance 方法,這個方法專門用來創建一個原始 Bean 實例。
這里就以 Spring 源碼中方法的執行順序為例來和小伙伴們分享。
1. doCreateBean
AbstractAutowireCapableBeanFactory#doCreateBean 就是 Bean 的創建方法,但是 Bean 的創建涉及到的步驟非常多,包括各種需要調用的前置后置處理器方法,今天我主要是想和大家聊聊單純的創建 Bean 的過程,其他方法咱們后面文章繼續。
在 doCreateBean 方法中,有如下一行方法調用:
protectedObject doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throwsBeanCreationException {
// Instantiate the bean.
BeanWrApper instanceWrapper = null;
if(mbd.isSingleton) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if(instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance;
//...
returnexposedObject;
}
createBeanInstance 這個方法就是真正的根據我們的配置去創建一個 Bean 了。
2. createBeanInstance
先來看源碼:
protectedBeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
if(beanClass != null&& !Modifier.isPublic(beanClass.getModifiers) && !mbd.isNonPublicAccessAllowed) {
thrownewBeanCreationException(mbd.getResourceDeion, beanName,
"Bean class isn't public, and non-public access not allowed: "+ beanClass.getName);
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier;
if(instanceSupplier != null) {
returnobtAInFromSupplier(instanceSupplier, beanName);
}
if(mbd.getFactoryMethodName != null) {
returninstantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
booleanresolved = false;
booleanautowireNecessary = false;
if(args == null) {
synchronized(mbd.constructorArgumentLock) {
if(mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if(resolved) {
if(autowireNecessary) {
returnautowireConstructor(beanName, mbd, null, null);
}
else{
returninstantiateBean(beanName, mbd);
}
}
// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if(ctors != null|| mbd.getResolvedAutowireMode == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues || !ObjectUtils.isEmpty(args)) {
returnautowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors;
if(ctors != null) {
returnautowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
returninstantiateBean(beanName, mbd);
}
這里就是核心的 Bean 的創建方法了,因此這個方法我來和大家詳細分析一下。
2.1 resolveBeanClass
這個方法是用來解析出來當前的 beanClass 對象,它的 核心邏輯就是根據我們在 XML 文件中配置的類的全路徑,通過反射加載出來這個 Class:
@Nullable
protectedClass<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)
throwsCannotLoadBeanClassException {
if(mbd.hasBeanClass) {
returnmbd.getBeanClass;
}
returndoResolveBeanClass(mbd, typesToMatch);
}
首先會調用 mbd.hasBeanClass 方法去判斷是否已經通過反射加載出來 beanClass 了,如果加載出來了就直接返回,沒有加載的話,就繼續執行下面的 doResolveBeanClass 去加載。
@Nullable什么時候會走 if 這條線呢?松哥舉一個例子,如果我們設置某一個 Bean 的 Scope 是 prototype 的話,那么當第二次獲取該 Bean 的實例的時候,就會走 if 這條線。
privateClass<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)
throwsClassNotFoundException {
//...
String className = mbd.getBeanClassName;
if(className != null) {
Object evaluated = evaluateBeanDefinitionString(className, mbd);
if(!className.equals(evaluated)) {
// A dynamically resolved expression, supported as of 4.2...
if(evaluated instanceofClass<?> clazz) {
returnclazz;
}
elseif(evaluated instanceofString str) {
className = str;
freshResolve = true;
}
else{
thrownewIllegalStateException( "Invalid class name expression result: "+ evaluated);
}
}
if(freshResolve) {
// When resolving against a temporary class loader, exit early in order
// to avoid storing the resolved Class in the bean definition.
if(dynamicLoader != null) {
returndynamicLoader.loadClass(className);
}
returnClassUtils.forName(className, dynamicLoader);
}
}
// Resolve regularly, caching the result in the BeanDefinition...
returnmbd.resolveBeanClass(beanClassLoader);
}
按理說,根據我們配置的類的全路徑加載出來一個 Class 應該是非常容易的,直接 Class.forName 就可以了。
但是!!!
如果對 Spring 用法比較熟悉的小伙伴就知道,配置 Class 全路徑的時候,我們不僅可以像下面這樣老老實實配置:
< beanclass= "org.JAVAboy.bean.Book"/>
我們甚至可以使用 SpEL 來配置 Bean 名稱,例如我有如下類:
publicclassBeanNameUtils{
publicString getName{
return"org.javaboy.bean.User";
}
}
這里有一個 getName 方法,這個方法返回的是一個類的全路徑,現在我們在 XML 文件中可以這樣配置:
< beanclass= "org.javaboy.bean.BeanNameUtils"id= "beanNameUtils"/>
< beanclass= "#{beanNameUtils.name}"id= "user"/>
在 XML 的 class 屬性中,我們可以直接使用 SpEL 去引用一個方法的執行,用該方法的返回值作為 class 的值。
了解了 Spring 中的這個玩法,再去看上面的源碼就很好懂了:
-
首先調用 mbd.getBeanClassName; 去獲取到類路徑。
-
接下來調用 evaluateBeanDefinitionString 方法進行 SpEL 運算,這個運算的目的是為了解析 className 中的 SpEL 表達式,當然,一般情況下 className 就是一個普通的字符串,不是 SpEL 表達式,那么解析完成之后就還是原本的字符串。如果是 className 是一個 SpEL,那么合法的解析結果分為兩種:
-
首先就是解析之后拿到了一個 Class,那這個就是我們想要的結果,直接返回即可。
-
要么就是解析出來是一個字符串,松哥上面舉的例子就是這種情況,那么就把這個字符串賦值給 className,并且將 freshResolve 屬性設置為 true,然后在接下來的 if 分支中去加載 Class。
-
當然,上面這些都是處理特殊情況,一般我們配置的普通 Bean,都是直接走最后一句 mbd.resolveBeanClass(beanClassLoader) ,這個方法的邏輯其實很好懂,我把代碼貼出來小伙伴們來瞅一瞅:
@Nullable
publicClass<?> resolveBeanClass( @NullableClassLoader classLoader) throwsClassNotFoundException {
String className = getBeanClassName;
if(className == null) {
returnnull;
}
Class<?> resolvedClass = ClassUtils.forName(className, classLoader);
this.beanClass = resolvedClass;
returnresolvedClass;
}
這個方法就相當直白了,根據 className 加載出來 Class 對象,然后給 beanClass 屬性也設置上值,這就和一開始的 if (mbd.hasBeanClass) 對應上了。
好了,到此,我們總算是根據 className 拿到 Class 對象了。
2.2 Supplier 和 factory-method
好了,回到一開始的源碼中,接下來該執行如下兩行代碼了:
Supplier<?> instanceSupplier = mbd.getInstanceSupplier;
if(instanceSupplier != null) {
returnobtainFromSupplier(instanceSupplier, beanName);
}
if(mbd.getFactoryMethodName != null) {
returninstantiateUsingFactoryMethod(beanName, mbd, args);
}
這兩個松哥在前面的文章中和小伙伴們已經講過了(Spring5 中更優雅的第三方 Bean 注入):前面的 obtainFromSupplier 方法是 Spring5 開始推出來的 Supplier,通過回調的方式去獲取一個對象;第二個方法 instantiateUsingFactoryMethod 則是通過配置的 factory-method 來獲取到一個 Bean 實例。
對這兩個方法不熟悉的小伙伴可以參考前面的文章:Spring5 中更優雅的第三方 Bean 注入。
2.3 re-create 邏輯
繼續回到一開始的源碼中,接下來是一段 re-create 的處理邏輯,如下:
booleanresolved = false;
booleanautowireNecessary = false;
if(args == null) {
synchronized(mbd.constructorArgumentLock) {
if(mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if(resolved) {
if(autowireNecessary) {
returnautowireConstructor(beanName, mbd, null, null);
}
else{
returninstantiateBean(beanName, mbd);
}
}
根據前面的介紹,我們現在已經獲取到 Class 對象了,接下來直接調用相應的構造方法就可以獲取到 Bean 實例了。但是這個 Class 對象可能存在多個構造方法,所以還需要一堆流程去確定到底調用哪個構造方法。
所以這里會先去判斷 resolvedConstructorOrFactoryMethod 是否不為空,不為空的話,說明這個 Bean 之前已經創建過了,該用什么方法創建等等問題都已經確定了,所以這次就不用重新再去確定了( resolved = true )。另一方面,autowireNecessary 表示構造方法的參數是否已經處理好了,這個屬性為 true 則表示構造方法的參數已經處理好了,那么就可以調用 autowireConstructor 方法去創建一個 Bean 出來,否則調用 instantiateBean 方法初始化 Bean。
這里涉及到的 autowireConstructor 和 instantiateBean 方法我們先不細說了,因為在后面還會再次涉及到。
2.4 構造器注入
繼續回到一開始的源碼中,接下來就是針對各種處理器的預處理了:
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if(ctors != null|| mbd.getResolvedAutowireMode == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues || !ObjectUtils.isEmpty(args)) {
returnautowireConstructor(beanName, mbd, ctors, args);
}
先來看 determineConstructorsFromBeanPostProcessors 方法,這個方法主要是考慮到你可能提供了 SmartInstantiationAwareBeanPostProcessor,松哥在前面的文章中和大家專門講過 BeanPostProcessor( BeanFactoryPostProcessor 和 BeanPostProcessor 有什么區別? ),這里的 SmartInstantiationAwareBeanPostProcessor 算是 BeanPostProcessor 的一種,也是 Bean 的一種增強器。SmartInstantiationAwareBeanPostProcessor 中有一個 determineCandidateConstructors 方法,這個方法返回某一個 Bean 的構造方法,將來可以通過這個構造方法初始化某一個 Bean。
我給大家舉一個簡單例子,假設我有如下類:
publicclassUser{
privateString username;
privateString address;
publicUser{
System.out.println( "=====no args=====");
}
publicUser(ObjectProvider<String> username){
System.out.println( "args==username");
this.username = username.getIfAvailable;
}
//省略 getter/setter/toString
}
現在我在 Spring 容器中注冊這個對象:
< beanclass= "org.javaboy.bean.User"id= "user">
</ bean>
按照我們已有的知識,這個將來會調用 User 的無參構造方法去完成 User 對象的初始化。
但是現在,假設我添加如下一個處理器:
publicclassMySmartInstantiationAwareBeanPostProcessorimplementsSmartInstantiationAwareBeanPostProcessor{
@Override
publicConstructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throwsBeansException {
if( "user".equals(beanName)) {
Constructor<?> constructor = null;
try{
constructor = beanClass.getConstructor(ObjectProvider . class) ;
} catch(NoSuchMethodException e) {
thrownewRuntimeException(e);
}
returnnewConstructor[]{constructor};
}
returnSmartInstantiationAwareBeanPostProcessor. super.determineCandidateConstructors(beanClass, beanName);
}
}
在 determineCandidateConstructors 方法中,返回一個有參構造方法,那么將來 Spring 容器會通過這里返回的有參構造方法去創建 User 對象,而不是通過無參構造方法去創建 User 對象。
最后,將這個處理器注冊到 Spring 容器:
< beanclass= "org.javaboy.bean.MySmartInstantiationAwareBeanPostProcessor"/>
現在,當我們啟動 Spring 容器的時候,User 就是通過有參構造方法初始化的,而不是無參構造方法。之所以會這樣,就是因為本小節一開始提到的源碼 determineConstructorsFromBeanPostProcessors ,這個方法就是去查看有無 SmartInstantiationAwareBeanPostProcessor,如果有,就調用對應的方法找到處理器并返回。
這個弄懂之后,if 中其他幾種情況就好理解了, mbd.getResolvedAutowireMode 是查看當前對象的注入方式,這個一般是在 XML 中配置的,不過日常開發中我們一般不會配置這個屬性,如果需要配置,方式如下:
< beanclass= "org.javaboy.bean.User"id= "user"autowire= "constructor">
</ bean>
如果添加了 autowire="constructor" 就表示要通過構造方法進行注入,那么這里也會進入到 if 中。
if 里邊剩下的幾個條件都好說,就是看是否有配置構造方法參數,如果配置了,那么也直接調用相應的構造方法就行了。
這里最終執行的是 autowireConstructor 方法,這個方法比較長,我就不貼出來了,和大家說一說它的思路:
-
首先把能獲取到的構造方法都拿出來,如果構造方法只有一個,且目前也沒有任何和構造方法有關的參數,那就直接用這個構造方法就行了。
-
如果第一步不能解決問題,接下來就遍歷所有的構造方法,并且和已有的參數進行參數數量和類型比對,找到合適的構造方法并調用。
繼續回到一開始的源碼中,接下來是這樣了:
ctors = mbd.getPreferredConstructors;
if(ctors != null) {
returnautowireConstructor(beanName, mbd, ctors, null);
}
這塊代碼看字面好理解,就是獲取到主構造方法,不過這個是針對 Kotlin 的,跟我們 Java 無關,我就不啰嗦了。
2.6 instantiateBean
最后就是 instantiateBean 方法了,這個方法就比較簡單了,我把代碼貼一下小伙伴們應該自己都能看明白:
protectedBeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd){
try{
Object beanInstance = getInstantiationStrategy.instantiate(mbd, beanName, this);
BeanWrapper bw = newBeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
returnbw;
}
catch(Throwable ex) {
thrownewBeanCreationException(mbd.getResourceDeion, beanName, ex.getMessage, ex);
}
}
@Override
publicObject instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner){
// Don't override the class with CGLIB if no overrides.
if(!bd.hasMethodOverrides) {
Constructor<?> constructorToUse;
synchronized(bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if(constructorToUse == null) {
finalClass<?> clazz = bd.getBeanClass;
if(clazz.isInterface) {
thrownewBeanInstantiationException(clazz, "Specified class is an interface");
}
try{
constructorToUse = clazz.getDeclaredConstructor;
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch(Throwable ex) {
thrownewBeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
returnBeanUtils.instantiateClass(constructorToUse);
}
else{
// Must generate CGLIB subclass.
returninstantiateWithMethodInjection(bd, beanName, owner);
}
}
從上面小伙伴么可以看到,本質上其實就是調用了 constructorToUse = clazz.getDeclaredConstructor; ,獲取到一個公開的無參構造方法,然后據此創建一個 Bean 實例出來。
3. 小結
好了,這就是 Spring 容器中 Bean 的創建過程,我這里單純和小伙伴們分享了原始 Bean 的創建這一個步驟,這塊內容其實非常龐雜,以后有空我會再和小伙伴們分享。
最后,給上面分析的方法生成了一個時序圖,小伙伴們作為參考。
其實看 Spring 源碼,松哥最大的感悟就是小伙伴們一定要了解 Spring 的各種用法,在此基礎之上,源碼就很好懂,如果你只會 Spring 一些基本用法,那么源碼一定是看得云里霧里的。
END