天天看點

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

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

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition(java.lang.String, org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.config.BeanDefinition)

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

然後在上一節講解的 getMergedBeanDefinition 中也打上斷點.

接下來debugger, 直接走到第二個斷點, 看一下調用棧:

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

org.springframework.beans.factory.support.AbstractBeanFactory#checkMergedBeanDefinition

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

點到 doGetBean 方法中, 然後打上斷點并運作到該位置, 這樣我們就跳過了上節的内容, 關注我們本節的内容, 加載BeanDefinition對應的Class是怎樣的.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

看這個地方關于 User 的 RootBeanDefinition 已經組裝好了,但是執行到這個階段的時候,我們隻知道了Bean的元資訊,還沒有告訴你它具體的一個Bean 創始的過程。

往後還要跳過一些細節,這裡的 dependsOn 數組為空, 是以直接跳一個if判斷過去

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

加載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<?>)

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

這裡getSingleton方法的傳參使用了一個lambda表達式的方式, 尤其是第二個參數, 其實另有玄機, 那麼我們先在 getSingleton 内部打個斷點:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

這裡可以看到 singletonObjects, 這個ConcurrentHashMap 之前有讨論過

spring-core-7-75 | 單例對象作為依賴來源:單體對象與普通Spring Bean存在哪些差異?

SingletonBeanRegistry#registerSingleton 會對其進行處理, 裡面儲存的單例對象, 我們前面知道這種注冊到Spring容器中的單例對象是沒有生命周期管理的, 相當于是一個外部對象. 同時因為這裡對其有多次get和set的操作, 是以需要加一個鎖保證多線程的安全.

那麼很顯然, 我們的User是來源于 BeanDefinition, 是以 singletonObjects裡面是不會儲存的, if (singletonObject == null) 判斷就進去了, 而不是擷取到Bean直接傳回, 往下運作也能夠發現這一點:

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

中間跳過一部分不太重要的代碼, 運作到

singletonObject = singletonFactory.getObject();

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

這裡的 singletonObject 是getSingleton方法的第二個入參, 之前我們注意到那裡是一個lambda表達式, 是以在裡面打上斷點:

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

createBean(beanName, mbd, args)

其實是調用了子類實作:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

那麼斷點打進去, 這裡面就開始建立Bean示例的流程了.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

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

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

那麼resolveBeanClass 會傳兩個參數進去,resolveBeanClass(mbd, beanName) 一個是 RootBeanDefinition,另一個是beanName,這個方法實際上有三個實參, 最後一個是可變長的, 沒有傳.

往下看,首先

if (mbd.hasBeanClass()) ,這個操作其實是告訴你目前 RootBeanDefinition 是不是存在這個bean的class的一個定義,就是如果之前Class解析好了就會直接傳回了.

org.springframework.beans.factory.support.AbstractBeanDefinition#hasBeanClass

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

但是請注意, 這裡判斷 this.beanClass instanceof Class, 而我們可以看一下目前 beanDefinition 中的定義

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

是個字元串,盡管它内容不為空,hasBeanClass 則判斷的是其是否為一個 Class,除非之前有調用過:

org.springframework.beans.factory.support.AbstractBeanDefinition#setBeanClass

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

否則 beanClass 仍然是一個字元串的形式, 根本不是一個完整的 Class 對象, 那麼這個判斷是判false的.

這裡會得到一個重要的表現, beanClass 在 BeanDefinition 對應的CLass加載完之前是一個String, 而加載後會變為一個Class類型, 後面會與這裡呼應.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

往下走, System.getSecurityManager() , 可見傳回 null, 涉及到我們前面講的 JavaSecurity, java的安全架構, 這了傳回null 說明系統的 安全管理器沒有被激活.

當然無論是進if判斷的哪一個分支, 能夠看到都會調用 doResolveBeanClass 方法, 隻不過前面那個套了一層 AccessController.doPrivileged(), 會有一個權限的控制, 那麼這個權限控制是什麼原理呢, 可以去 ClassLoader中了解一下:

拐個彎, 去看一下java的安全控制問題

java.lang.ClassLoader#loadClass(java.lang.String)

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

通常來說在ClassLoader去load一個類的時候,會調用一個 loadClass() 方法,不過很少人會注意它, 這個方法裡面其實暗藏玄機,這個方法裡面其實并不是所有的class都是允許被加載的,它其實是有安全的一個限制的.

loadClass就要涉及到 ClassLoader 的選擇, 再去看另一個方法,

java.lang.ClassLoader#getSystemClassLoader

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

看到注釋, 這個裡面其實有一個 SecurityException,注釋說這裡會有 checkPermission,會去檢查權限,權限如果允許的話,才可以去擷取 SystemClassLoader,也就是說ClassLoader的擷取基本上需要安全的一個許可,隻是通常我們的Permission全部都是允許的,就是grant的一個相應的權限,它一般都是 all , 相當于說所有的權限,比如檔案讀取權限, classLoader權限, 反射的權限都是允許的。

那麼同樣的方式,另外看一下,在 Thread 裡面,

java.lang.Thread#getContextClassLoader

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

Thread裡面有一個 getContextClassLoader 方法,它這裡也會有個安全的限制,你會看到最後一個方法也會去檢查 SecurityManager已經激活的情況下,check他是不是有權限,是以這是需要安全的一個開關來進行控制的.

當然通常情況這個SecurityManager就等于空,是以可以直接去擷取ContextClassLoader,但是其實并不代表它就是一定沒有這個疑慮。是以這就是我們說BeanDefinition在讀取ClassLoader的時候它的一個控制。

回到主題, AbstractBeanFactory#resolveBeanClass

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

接下來看到處理的方法就是 doResolveBeanClass 方法,這個方法就是從 BeanDefinition裡面去得到我們的相關的類,兩個參數就不說了, 進去看。

org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

打斷點下來,這裡就會存在一個細節,就是說這裡會有 兩個Classloader,一個是我們的目前BeanFactory的,另外一個是 dynamicLoader。前面我們提到了在 ConfigurableBeanFactory 臨時ClassLoader,那麼這個就在這邊展現了.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

往下走代碼,第一個 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 是可以替換的.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

回到 doResolveBeanClass 内, 再往下面看,前面知道 typesToMatch 其實為空,是以這個if判斷都不會進.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

那麼再往下面看,

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

那麼這個 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 可能是不一緻的. 這裡不說了,我們目前的例子沒有這樣的情況.

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

往下走, freshResolve 也是 false,是以又跳過了一部分

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

直接看

又回到了 BeanDefinition裡面了?! 那前面這麼複雜是在做啥子麼!

AbstractBeanDefinition#resolveBeanClass, 折騰了這麼久, 先進的 AbstractBeanFactory#resolveBeanClass ,又回到了BeanDefinition

org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段
spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

進去看看,第一行擷取 className,

接下來就是 ClassUtils.forName(className, classLoader); ClassUtils 就是由spring提供的一個擷取Class類的工具類, 那麼其中擷取Class的關鍵就是去調用 Class.forName(), 裡面其實還是使用了java傳統的ClassLoader來進行ClassLoading.

org.springframework.util.ClassUtils#forName

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

調用完之後這裡User對應的BeanDefinition内部發生了一個很重要的變化

spring-core-9-92 | Spring Bean Class加載階段:BD對應的Class是怎樣加載的?Spring Bean Class 加載階段

注意這裡 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加載之後,那麼要如何進行執行個體化.

繼續閱讀