Spring Bean Class 加載階段
上一節分析了關于 BeanDefinition 合并的細節過程, 那麼接下來在 Spring Bean 被建立之前, 需要将Bean對應的class進行加載. 那麼這裡就要涉及到一些細節需要理清.
ClassLoader 類加載
第一個涉及到 java 裡面的一些基礎, ClassLoader , 這個東西其實接觸架構的同學會更加熟悉, java的類加載主要是依靠 ClassLoader 來進行的.
可以參考:
java ClassLoader詳解
Java Security 安全控制
第二個是關于java security,就是Java安全裡面的一些控制,在spring早期是沒有增加這塊的實作的,後期開始增加了一些相關的控制,多提一點,過去是spring在實作的時候,在 IoC 的實作DI和我們說依賴注入和依賴查找的時候,他沒有注重關于Java Security的一個安全整合,不過在這方面 Java EE 規範裡面有清晰的描述,是以後續在目前版本看的時候,會發現裡面有一些 Java Security API 的代碼,後面會講到這些代碼的作用.
ConfigurableBeanFactory 臨時ClassLoader
第三個方面,這個方面是個大的方面,這個方面本節其實選擇性的跳過,後面會帶入一個簡單的場景和大家分析一下,這個場景實際上是非常有限的,通常我們是用不到的。
代碼示例
java.lang.ClassLoader
org.springframework.beans.factory.config.BeanDefinition
org.springframework.beans.factory.support.AbstractBeanFactory
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
org.springframework.util.ClassUtils
java.lang.Class
org.geekbang.thinking.in.spring.bean.lifecycle.MergedBeanDefinitionDemo
從問題開始, BeanDefinition 是怎麼 從文本的元資訊形式加載到 Class 的?
好,我們還是回到idea裡面來,前面章節知道了 BeanDefinition 在合并之後會生成 RootBeanDefinition。那麼接下來有很重要的一個操作,就是我們前面看到過 BeanDefinition 實際上裡面有一個很重要的資訊,這個是我們的bean的一個基本的來源,也就是說它會 getBeanClassName 。
org.springframework.beans.factory.config.BeanDefinition#getBeanClassName
/**
* Return the current bean class name of this bean definition.
* <p>Note that this does not have to be the actual class name used at runtime, in
* case of a child definition overriding/inheriting the class name from its parent.
* Also, this may just be the class that a factory method is called on, or it may
* even be empty in case of a factory bean reference that a method is called on.
* Hence, do <i>not</i> consider this to be the definitive bean type at runtime but
* rather only use it for parsing purposes at the individual bean definition level.
* @see #getParentName()
* @see #getFactoryBeanName()
* @see #getFactoryMethodName()
*/
@Nullable
String getBeanClassName();
前面我們也分析過,其實 BeanDefinition 它是一個關于Bean的元資訊的配置或者是描述,那麼事實上它是通過文本的方式來進行呈現的,它這裡并沒有顯式的告訴你它的類是什麼,我們知道Java裡面類的描述是通過Class來進行表達,那麼Class又是由 ClassLoader 來進行加載的,是以這個BeanDefinition 執行個體化這一過程和我們的傳統的Java的作用機制肯定是跑不掉的。
java.lang.ClassLoader
/**
* A class loader is an object that is responsible for loading classes. The
* class <tt>ClassLoader</tt> is an abstract class. Given the <a
* href="#name" target="_blank" rel="external nofollow" >binary name</a> of a class, a class loader should attempt to
* locate or generate data that constitutes a definition for the class. A
* typical strategy is to transform the name into a file name and then read a
* "class file" of that name from a file system.
*
*/
public abstract class ClassLoader {
事實上在 Spring IoC容器的實作裡面,ClassLoader 加載是一個邊緣化的操作(其實是封裝的太深了…),通常我們不太重視,接下來我們會通過一個方法的方式來進行操作。
首先我們看回之前的示例 MergedBeanDefinitionDemo,然後在依賴查找的位置打上斷點:
org.geekbang.thinking.in.spring.bean.lifecycle.MergedBeanDefinitionDemo
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SNycjNkdzMhVTN3YjZ4MzN0gzY5YmZxcDM4MzMhJGMk9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition(java.lang.String, org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.config.BeanDefinition)
然後在上一節講解的 getMergedBeanDefinition 中也打上斷點.
接下來debugger, 直接走到第二個斷點, 看一下調用棧:
org.springframework.beans.factory.support.AbstractBeanFactory#checkMergedBeanDefinition
點到 doGetBean 方法中, 然後打上斷點并運作到該位置, 這樣我們就跳過了上節的内容, 關注我們本節的内容, 加載BeanDefinition對應的Class是怎樣的.
看這個地方關于 User 的 RootBeanDefinition 已經組裝好了,但是執行到這個階段的時候,我們隻知道了Bean的元資訊,還沒有告訴你它具體的一個Bean 創始的過程。
往後還要跳過一些細節,這裡的 dependsOn 數組為空, 是以直接跳一個if判斷過去
加載Class的流程, 是嵌套在spring執行個體化Bean的流程中的
好,這裡注意注釋, Create bean instance. 關注點來了. 首先 mbd.isSingleton() 目前沒有指定 User 的 scope,是以這裡bean 預設是 singleton, 往下走
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
這裡getSingleton方法的傳參使用了一個lambda表達式的方式, 尤其是第二個參數, 其實另有玄機, 那麼我們先在 getSingleton 内部打個斷點:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
這裡可以看到 singletonObjects, 這個ConcurrentHashMap 之前有讨論過
spring-core-7-75 | 單例對象作為依賴來源:單體對象與普通Spring Bean存在哪些差異?
SingletonBeanRegistry#registerSingleton 會對其進行處理, 裡面儲存的單例對象, 我們前面知道這種注冊到Spring容器中的單例對象是沒有生命周期管理的, 相當于是一個外部對象. 同時因為這裡對其有多次get和set的操作, 是以需要加一個鎖保證多線程的安全.
那麼很顯然, 我們的User是來源于 BeanDefinition, 是以 singletonObjects裡面是不會儲存的, if (singletonObject == null) 判斷就進去了, 而不是擷取到Bean直接傳回, 往下運作也能夠發現這一點:
中間跳過一部分不太重要的代碼, 運作到
singletonObject = singletonFactory.getObject();
這裡的 singletonObject 是getSingleton方法的第二個入參, 之前我們注意到那裡是一個lambda表達式, 是以在裡面打上斷點:
createBean(beanName, mbd, args)
其實是調用了子類實作:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
那麼斷點打進去, 這裡面就開始建立Bean示例的流程了.
AbstractBeanFactory#resolveBeanClass 加載BeanClass的關鍵方法
首先 RootBeanDefinition 已經得到了,得到之後往下看,這裡就會有 resolveBeanClass 方法的調用, 看注釋說明
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
確定bean的class已經在該點被确實的解析, 并且克隆 bean definition,
以防在動态解析class時,
因為merged beanDefinition 是共享的因而不能被存儲
是以這個方法涉及class從 beanDefinition 的解析, 是很重要的, 轉進去看
org.springframework.beans.factory.support.AbstractBeanFactory#resolveBeanClass
那麼resolveBeanClass 會傳兩個參數進去,resolveBeanClass(mbd, beanName) 一個是 RootBeanDefinition,另一個是beanName,這個方法實際上有三個實參, 最後一個是可變長的, 沒有傳.
往下看,首先
if (mbd.hasBeanClass()) ,這個操作其實是告訴你目前 RootBeanDefinition 是不是存在這個bean的class的一個定義,就是如果之前Class解析好了就會直接傳回了.
org.springframework.beans.factory.support.AbstractBeanDefinition#hasBeanClass
但是請注意, 這裡判斷 this.beanClass instanceof Class, 而我們可以看一下目前 beanDefinition 中的定義
是個字元串,盡管它内容不為空,hasBeanClass 則判斷的是其是否為一個 Class,除非之前有調用過:
org.springframework.beans.factory.support.AbstractBeanDefinition#setBeanClass
否則 beanClass 仍然是一個字元串的形式, 根本不是一個完整的 Class 對象, 那麼這個判斷是判false的.
這裡會得到一個重要的表現, beanClass 在 BeanDefinition 對應的CLass加載完之前是一個String, 而加載後會變為一個Class類型, 後面會與這裡呼應.
往下走, System.getSecurityManager() , 可見傳回 null, 涉及到我們前面講的 JavaSecurity, java的安全架構, 這了傳回null 說明系統的 安全管理器沒有被激活.
當然無論是進if判斷的哪一個分支, 能夠看到都會調用 doResolveBeanClass 方法, 隻不過前面那個套了一層 AccessController.doPrivileged(), 會有一個權限的控制, 那麼這個權限控制是什麼原理呢, 可以去 ClassLoader中了解一下:
拐個彎, 去看一下java的安全控制問題
java.lang.ClassLoader#loadClass(java.lang.String)
java.lang.ClassLoader#loadClass(java.lang.String, boolean)
通常來說在ClassLoader去load一個類的時候,會調用一個 loadClass() 方法,不過很少人會注意它, 這個方法裡面其實暗藏玄機,這個方法裡面其實并不是所有的class都是允許被加載的,它其實是有安全的一個限制的.
loadClass就要涉及到 ClassLoader 的選擇, 再去看另一個方法,
java.lang.ClassLoader#getSystemClassLoader
看到注釋, 這個裡面其實有一個 SecurityException,注釋說這裡會有 checkPermission,會去檢查權限,權限如果允許的話,才可以去擷取 SystemClassLoader,也就是說ClassLoader的擷取基本上需要安全的一個許可,隻是通常我們的Permission全部都是允許的,就是grant的一個相應的權限,它一般都是 all , 相當于說所有的權限,比如檔案讀取權限, classLoader權限, 反射的權限都是允許的。
那麼同樣的方式,另外看一下,在 Thread 裡面,
java.lang.Thread#getContextClassLoader
Thread裡面有一個 getContextClassLoader 方法,它這裡也會有個安全的限制,你會看到最後一個方法也會去檢查 SecurityManager已經激活的情況下,check他是不是有權限,是以這是需要安全的一個開關來進行控制的.
當然通常情況這個SecurityManager就等于空,是以可以直接去擷取ContextClassLoader,但是其實并不代表它就是一定沒有這個疑慮。是以這就是我們說BeanDefinition在讀取ClassLoader的時候它的一個控制。
回到主題, AbstractBeanFactory#resolveBeanClass
接下來看到處理的方法就是 doResolveBeanClass 方法,這個方法就是從 BeanDefinition裡面去得到我們的相關的類,兩個參數就不說了, 進去看。
org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass
打斷點下來,這裡就會存在一個細節,就是說這裡會有 兩個Classloader,一個是我們的目前BeanFactory的,另外一個是 dynamicLoader。前面我們提到了在 ConfigurableBeanFactory 臨時ClassLoader,那麼這個就在這邊展現了.
往下走代碼,第一個 beanClassLoader,就是目前的 AppClassLoader, 而第二個dynamicLoader 其實也是它, 這些都是jvm提供給我們的spring 容器的.
另外提到一個細節,就是說有時候我們去得到這個 beanClassLoader,我們需要用到一個 BeanClassLoaderAware,後面會講到它,
package org.springframework.beans.factory;
/**
* Callback that allows a bean to be aware of the bean
* {@link ClassLoader class loader}; that is, the class loader used by the
* present bean factory to load bean classes.
*
* <p>This is mainly intended to be implemented by framework classes which
* have to pick up application classes by name despite themselves potentially
* being loaded from a shared class loader.
*
* <p>For a list of all bean lifecycle methods, see the
* {@link BeanFactory BeanFactory javadocs}.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.0
* @see BeanNameAware
* @see BeanFactoryAware
* @see InitializingBean
*/
public interface BeanClassLoaderAware extends Aware {
/**
* Callback that supplies the bean {@link ClassLoader class loader} to
* a bean instance.
* <p>Invoked <i>after</i> the population of normal bean properties but
* <i>before</i> an initialization callback such as
* {@link InitializingBean InitializingBean's}
* {@link InitializingBean#afterPropertiesSet()}
* method or a custom init-method.
* @param classLoader the owning class loader
*/
void setBeanClassLoader(ClassLoader classLoader);
}
這個接口會把 ClassLoader 傳進來,這個 ClassLoader 就是BeanFactory的那裡擷取的 ClassLoader.
那麼有這個接口也就印證了一個問題, Spring的ClassLoader 是可以替換的.
回到 doResolveBeanClass 内, 再往下面看,前面知道 typesToMatch 其實為空,是以這個if判斷都不會進.
那麼再往下面看,
那麼這個 BeanClassName Spring容器中有可能會不一緻, 看後面的處理
org.springframework.beans.factory.support.AbstractBeanFactory#evaluateBeanDefinitionString
@Nullable
protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) {
if (this.beanExpressionResolver == null) {
return value;
}
Scope scope = null;
if (beanDefinition != null) {
String scopeName = beanDefinition.getScope();
if (scopeName != null) {
scope = getRegisteredScope(scopeName);
}
}
return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}
往下走, 這裡面就會去做一個操作。BeanClassName名稱存在,并且不等于空的時候,這裡會去評估一下,裡面的代碼挺長, 但其實和我們的主流程關系不大, 就是把目前的 BeanClassName 給傳回出來,請注意一下,有時候BeanClassName和 evaluated 可能是不一緻的. 這裡不說了,我們目前的例子沒有這樣的情況.
往下走, freshResolve 也是 false,是以又跳過了一部分
直接看
又回到了 BeanDefinition裡面了?! 那前面這麼複雜是在做啥子麼!
AbstractBeanDefinition#resolveBeanClass, 折騰了這麼久, 先進的 AbstractBeanFactory#resolveBeanClass ,又回到了BeanDefinition
org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass
進去看看,第一行擷取 className,
接下來就是 ClassUtils.forName(className, classLoader); ClassUtils 就是由spring提供的一個擷取Class類的工具類, 那麼其中擷取Class的關鍵就是去調用 Class.forName(), 裡面其實還是使用了java傳統的ClassLoader來進行ClassLoading.
org.springframework.util.ClassUtils#forName
java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)
調用完之後這裡User對應的BeanDefinition内部發生了一個很重要的變化
注意這裡 beanClass過去是什麼,過去是String,擷取到Class之後, 它這裡做了一個交換,把過去的資料類型變成一個Class類型,至此我們需要的BeanDefinition所對應的Class才真正擷取完.
一個簡單的步驟:
AbstractBeanFactory#resolveBeanClass中将string類型的beanClass 通過目前線程Thread.currentThread().getContextClassLoader(),Class.forName來擷取class對象,将beanClass變為Class類型對象.
總結
剛才通過簡單的示例調試出, BeanDefinition,在ClassLoading中最終還是運用到傳統的 Java ClassLoader,隻不過在調用過程中,它會涉及到一些Java安全的細節操作,實際上這個操作在Java本身就已經具備了,而是我們通常沒有把它激活因而會忽略調。
那麼接下來會進入到一個重要環節,就是當BeanDefinition從配置解析以及合并再到Class加載之後,那麼要如何進行執行個體化.