spring-core-9-92 | Spring Bean Class加载阶段:BD对应的Class是怎样加载的?

763 阅读11分钟

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">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 org.geekbang.thinking.in.spring.bean.lifecycle.MergedBeanDefinitionDemo 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

image.png

接下来看到处理的方法就是 doResolveBeanClass 方法,这个方法就是从 BeanDefinition里面去得到我们的相关的类,两个参数就不说了, 进去看。

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

image.png

打断点下来,这里就会存在一个细节,就是说这里会有 两个Classloader,一个是我们的当前BeanFactory的,另外一个是 dynamicLoader。前面我们提到了在 ConfigurableBeanFactory 临时ClassLoader,那么这个就在这边体现了.

image.png

往下走代码,第一个 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 是可以替换的.

image.png

回到 doResolveBeanClass 内, 再往下面看,前面知道 typesToMatch 其实为空,所以这个if判断都不会进.

image.png

那么再往下面看,

mbd.getBeanClassName();

image.png

那么这个 BeanClassName Spring容器中有可能会不一致, 看后面的处理

Object evaluated = evaluateBeanDefinitionString(className, mbd);

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 可能是不一致的. 这里不说了,我们当前的例子没有这样的情况.

image.png

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

image.png

直接看

mbd.resolveBeanClass(beanClassLoader);

又回到了 BeanDefinition里面了?! 那前面这么复杂是在做啥子么!

AbstractBeanDefinition#resolveBeanClass, 折腾了这么久, 先进的 AbstractBeanFactory#resolveBeanClass ,又回到了BeanDefinition

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

image.png

进去看看,第一行获取 className,

Class<?> resolvedClass = ClassUtils.forName(className, classLoader);

接下来就是 ClassUtils.forName(className, classLoader); ClassUtils 就是由spring提供的一个获取Class类的工具类, 那么其中获取Class的关键就是去调用 Class.forName(), 里面其实还是使用了java传统的ClassLoader来进行ClassLoading.

org.springframework.util.ClassUtils#forName image.png

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

调用完之后这里User对应的BeanDefinition内部发生了一个很重要的变化

image.png

注意这里 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加载之后,那么要如何进行实例化.