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.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判断都不会进.
那么再往下面看,
mbd.getBeanClassName();
那么这个 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 可能是不一致的. 这里不说了,我们当前的例子没有这样的情况.
往下走, freshResolve 也是 false,所以又跳过了一部分
直接看
mbd.resolveBeanClass(beanClassLoader);
又回到了 BeanDefinition里面了?! 那前面这么复杂是在做啥子么!
AbstractBeanDefinition#resolveBeanClass, 折腾了这么久, 先进的 AbstractBeanFactory#resolveBeanClass ,又回到了BeanDefinition
org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass
进去看看,第一行获取 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
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加载之后,那么要如何进行实例化.