您的位置:首页 > 编程语言 > Java开发

SpringBoot源码解析01----@Bean方法的套娃调用原理

2020-03-18 19:07 477 查看

SpringBoot源码解析 第一篇----@Bean方法的套娃调用原理

前言:

​ 本篇攻略是我技术文章系列的处女作,也作为一个记录的开始。

​ 以前也阅读过许多源码,但最终印证了好记性不如烂笔头的道理,所以还是要多做笔记呀。

​ 本系列不会分析那些刻板的执行流程,网上多的是。我会根据开发中经常遇到的***知其然而不知其所以然***的问题从源码入手分析来解惑。

​ 本人目前新手水平,如果分析的有问题,希望读者大佬们能及时指点纠错。

先举个栗子

@Configuration
public class TestConfiguration {

@Bean
public CommonDependency commonDependency() {
return new CommonDependency();
}

@Bean
public OneServer oneServer() {
return new OneServer(commonDependency());
}

@Bean
public AnotherServer anotherServer() {
return new AnotherServer(commonDependency());
}
}

​ 上文代码作为SpringBoot中的配置类,大家都不陌生。其中后两个bean都依赖于commonDependency实例,那么问题来了,后两个bean都调用了第一个bean的new的方法,那么最后容器中到底有几个commonDependency实例呢? 大部分小伙伴们都会说就一个,因为Spring Bean默认的scope是单例的,但明明又调用了new方法,总有点说不通的地方。

​ 那么SpringBoot内部是如何实现这种写法而不重复创建实例呢?这里就需要小伙伴们对Spring Bean的加载以及实例化机制、BeanFactoryPostProcessor后置处理器的机制、CGLIB动态代理技术有稍微一些了解才可,本文中不会详细讲解这些内容。

​ 在从发现问题到研究明白的过程中,我会以断点调试的思路,一步步地引导并讲解源码,希望小伙伴们能静下心来阅读,最好打开IDE边看源码边阅读,理解更深哦。

​ 首先,给return new CommonDependency()这句话中打上断点,发现程序在此处只执行了一次,说明其他两个bean虽然调用其方法,但并没有走这。这是一条相当有用的信息,相信大家已经明白了,这个方法已经被代理了,在执行目标行前已经被拦截掉并返回。

// 调用栈看着心烦的直接忽略 不影响阅读文章

commonDependency:15, TestConfiguration (com.example.demo)
CGLIB$commonDependency$2:-1, TestConfiguration$$EnhancerBySpringCGLIB$$670cda94 (com.example.demo)
invoke:-1, TestConfiguration$$EnhancerBySpringCGLIB$$670cda94$$FastClassBySpringCGLIB$$eeddf75b (com.example.demo)
invokeSuper:244, MethodProxy (org.springframework.cglib.proxy)
intercept:363, ConfigurationClassEnhancer$BeanMethodInterceptor (org.springframework.context.annotation)
commonDependency:-1, TestConfiguration$$EnhancerBySpringCGLIB$$670cda94 (com.example.demo)
...

​ 从调用栈上我们也可以看出,TestConfiguration类已经变成了

TestConfiguration$$EnhancerBySpringCGLIB$$670cda94

使用CGLIB生成了子类。至于为什么使用CGLIB而不用JDK动态代理、代理的时机等这种支线任务我们先抛开不看,我们先做主线。如果不明白为啥要用代理,请先移步设计模式学习。

​ 这里要涉及到CGLIB的概念,我只简单对照JDK的Proxy代理说一下,如果需要深入了解,可以研究这位大佬的攻略:

https://www.iteye.com/blogs/subjects/cglib-in-action

​ 我们想要知道代理中究竟做了什么神奇逻辑,那么我们首先就要找到这个代理类的增强方法(如果是Proxy代理的话,增强逻辑都是写在InvocationHandler里的, GFLIB这里是MethodInterceptor),调用栈中正好呼应上了这个类,ConfigurationClassEnhancer$BeanMethodInterceptor,即ConfigurationClassEnhancer中的一个内部类,那么源码分析从这里开始(注意保持清醒)。

ConfigurationClassEnhancer位于Spring context.annotation包,中文意思是*注解配置类的增强器。它是本攻略中的一个核心的选手。根据源码注释,

* Enhances {@link Configuration} classes by generating a CGLIB subclass which
* interacts with the Spring container to respect bean scoping semantics for
* {@code @Bean} methods. Each such {@code @Bean} method will be overridden in
* the generated subclass, only delegating to the actual {@code @Bean} method
* implementation if the container actually requests the construction of a new
* instance. Otherwise, a call to such an {@code @Bean} method serves as a
* reference back to the container, obtaining the corresponding bean by name.

// 这段话大致意思就是说 这个选手是用来通过CGLIB生成子类来增强那些@Configuration配置类,
// 对各种作用域的@Bean返回的实例的处理;也就是重写这些带@Bean的方法,
// 根据每个bean的作用域去判断调用是该产生新实例还是返回旧实例的引用,拿bean名字判断。

大概明白了这个类做的事就是筛选拦截的配置类对象及方法、组织增强行为、以及进行增强生成代理。有跟着看源码的小伙伴会问了,这个类也不受IoC容器管理有啥用,这个问题放到后面解释,角色要轮流登场。

在这个类中,规定了三种增强策略(CGLIB的回调)

private static final Callback[] CALLBACKS = new Callback[] {
// 对@Bean方法的增强
new BeanMethodInterceptor(),
// 如果实现了BeanFactoryAware, 对setBeanFactory()方法增强
new BeanFactoryAwareMethodInterceptor(),
// 原封不动
NoOp.INSTANCE
};

Spring怎样确定配置类中的哪些方法需要增强,就用到了CGLIB中的回调过滤CallbackFilter。当对每个方法进行增强前,就会调用

// 内部类
private static class ConditionalCallbackFilter implements CallbackFilter {

private final Callback[] callbacks;

private final Class<?>[] callbackTypes;

// 构造时 需放入所有增强策略
public ConditionalCallbackFilter(Callback[] callbacks) {
this.callbacks = callbacks;
this.callbackTypes = new Class<?>[callbacks.length];
for (int i = 0; i < callbacks.length; i++) {
this.callbackTypes[i] = callbacks[i].getClass();
}
}

// 当每个方法被重写时,都需调用该方法判断以哪种增强策略进行重写
@Override
public int accept(Method method) {
for (int i = 0; i < this.callbacks.length; i++) {
Callback callback = this.callbacks[i];
if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
return i;
}
}
throw new IllegalStateException("No callback available for method " + method.getName());
}

...
}

其中ConditionalCallback接口是对所有增强(回调)策略的实现类的统一要求,要求每个增强逻辑都提供判断【某方法是否适用此策略】(这种方式很常见,源码里很多地方都有)

// 内部接口
private interface ConditionalCallback extends Callback {
boolean isMatch(Method candidateMethod);
}

因为我们要研究@Bean方法 所以只需要看BeanMethodInterceptor策略实现

// 内部类
private static class BeanMethodInterceptor
// 实现了MethodInterceptor 即要实现增强逻辑
// 实现了ConditionalCallback 即要提供能否适用的匹配方法
implements MethodInterceptor, ConditionalCallback {

@Override
@Nullable
// 增强逻辑 类比JDK动态代理中InvocationHandler 都差不多的
// enhancedConfigInstance CGLib动态生成的代理类实例
// beanMethod 被调用的方法引用
// beanMethodArgs 这个方法的参数列表
// cglibMethodProxy 代理方法引用
public Object intercept(Object enhancedConfigInstance,
Method beanMethod,
Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {

// 获取到装他的IoC容器
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

// 获取依赖的bean的名字
// 这个BeanAnnotationHelper就是一个保存beanMethod和beanName的一个缓存映射 方便寻找依赖关系
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

// 这里判断 原@Bean方法所依赖的其他实例是否为代理依赖(涉及到其他问题,与本文无相干)
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}

// PS:这里我没太看懂(这里的调用栈太深了) 当bean工厂中已经存在了该FactoryBean
// 当不是代理依赖的时候,就使用JDK代理或者CGLIB代理产生一个新的bean返回 ???
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX 										+ beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
}
else {
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), 								beanFactory, beanName);
}
}

// 判断是否为当前工厂调用的方法
// 这里不太好理解 当依赖@Bean方法在上 被依赖@Bean方法在下面时,比如oneServer()在前,		   		  // commonDependency实例还没被创建,那么此时这里为true,会调用代理方法的父类方法(即原方法)
// 原方法会去调用commonDependency() 此方法依旧带有@Bean注解又被拦截,再次走到这里时,
// 这里的判断就为false 因为当前工厂调用的方法 仍然是oneServer()
// 鱿鱼丝会进入到resolveBeanReference方法中 后面会分析方法内部
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
...
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

}

在上方的intercept方法中,读了半天发现又是套娃。那么我们就要看看这个resolveBeanReference到底做了什么。

private Object resolveBeanReference(Method beanMethod,
Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory,
String beanName) {
// 4个参数 分别是 调用的方法 方法参数列表 容器 以及这个bean的name

// 这里要判断目前要获取的这个bean 的创建状态 是否正在创建
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
try {
// 官方注解的意思是 在某些情况下 这个bean可能会在用户直接或间接的行为下标记为已经创建
// 为了保证不出错 暂时性把这个状态改成false 处理完成后再修改回去
if (alreadyInCreation) {
beanFactory.setCurrentlyInCreation(beanName, false);
}
// 这里判断有没有参数要传
boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
if (useArgs && beanFactory.isSingleton(beanName)) {
for (Object arg : beanMethodArgs) {
if (arg == null) {
useArgs = false;
break;
}
}
}
// ※ 这里终于到重点了 增强逻辑会从IoC容器中尝试着拿这个bean (有可能没有)
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
beanFactory.getBean(beanName));
// 如果拿出来的bean 不是返回的类型
if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
// 如果容器中不存在此Name的bean 会返回一个NullBean  这里这么调用没问题
if (beanInstance.equals(null)) {
...
beanInstance = null;
}
else {
// 如果类型都对不上 直接报错...
...
try {
BeanDefinition beanDefinition = 																beanFactory.getMergedBeanDefinition(beanName);
msg += " Overriding bean of same name declared in: " + 										beanDefinition.getResourceDescription();
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore - simply no detailed message then.
}
throw new IllegalStateException(msg);
}
}

// 走到这里 要么是已经拿出了合适的bean 要么是没有
// 这里是使用了实例化策略中记录的当前工厂调用的方法 内部维护的是一个ThreadLocal
// 从之前的缓存映射中找到这个方法的beanName 然后把bean的依赖关系注册到容器中

Method currentlyInvoked = 																	SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
// currentlyInvoked代表了工厂调用配置类的根方法
if (currentlyInvoked != null) {
String outerBeanName = 																	BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
beanFactory.registerDependentBean(beanName, outerBeanName);
}
return beanInstance;
}
finally {
// 最后再还原修改过的bean创建状态
if (alreadyInCreation) {
beanFactory.setCurrentlyInCreation(beanName, true);
}
}
}

其实@Bean方法调用@Bean方法时 会通过增强 先从IoC容器中获取,当获取不到时才会创建并加载为FactoryBean。

​ 而什么时候配置类对象就变成了代理子类了呢?这个增强器是什么时候被SpringBoot所用的呢?这时候就需要配合其他支线任务去解释了。(后面的部分需要两个类的代码穿插讲解)

ConfigurationClassPostProcessor 配置类后置处理器 一个Spring中非常强大的后置处理器登场(功能太多,注解取代XML文件的操作大多都在这里,本文之说相干的部分)。在这里bean工厂的一些概念就不进行讲解了,不懂的小伙伴们需要多了解一下。它的作用就是在bean工厂启动收集完所有BeanDefinition后,优先找到

直接上代码

// 在它的后置处理钩子方法中 做的就是把所有选中的配置类进行CGLIB增强

// 这里顺便说一下 为什么要用CGLIB而不是JDK代理  JDK代理的前提是必须实现有接口且约束规范
// 而在配置类中所配置的内容种类繁多 并无规范可言 而且可以嵌套 所以CGLIB的优势就凸显了
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// 这里做的就是对每个beanDefinition进行标记是否为配置类
// 但这个if不会被执行,此操作在postProcessBeanDefinitionRegistry方法中已经完成
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}

// 主要我们需要研究此方法
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

继续套娃,阅读enhanceConfigurationClasses方法()

// 从对带有标记的beanDefinition,使用ConfigurationClassEnhancer进行代理增强
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.
CONFIGURATION_CLASS_ATTRIBUTE);
...

// 这里只代理Full类型的 也就是@Configuration标注的类 而且没有关掉注解proxyBeanMethod开			  // 关的
// 这里又涉及到众多要素 我就不解释Full Site Null 类型了 源码在ConfigurationClassUtils
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) 			  {
...
// 把符合的beanDefinition放入map 后面操作
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
// 没配置类就不增强了
if (configBeanDefs.isEmpty()) {
return;
}

// ※ 这里就创建了上面讲到了增强器
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
// 遍历刚才收集到的配置类beanDefinition
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) 		  {
AbstractBeanDefinition beanDef = entry.getValue();
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, 						Boolean.TRUE);
Class<?> configClass = beanDef.getBeanClass();
// 这里调用了增强器的代理方法
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
...
// 把代理过的Class类型替换到原先的BeanDefinition中
beanDef.setBeanClass(enhancedClass);
}
}
}

我们发现是BeanFactory后置处理时,创建了增强器并在bean工厂中搜索到配置类的bean信息,对其骨架进行了增强修改,而此时TestConfiguration还并未出生;当然,***等他出生时,他已经不是他***了。

​ 最后我们跳回到ConfigurationClassEnhancer中,解决余下的问题,生成增强的代理。

// 该方法被后置处理器中的实例调用
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 怎样判断一个类信息已经被我的逻辑增强过了 就是使用EnhancedConfiguration这个标记接口 其实就是
// BeanFactoryAware 子接口用作标记
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
...
return configClass;
}
// 没被增强过的就增强
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
...
return enhancedClass;
}

// 增强方法 CGLIB使用
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
// 原配置类作为代理类的父类
enhancer.setSuperclass(configSuperClass);
// 追加一个标记接口 2个作用
// 1:用来标记生出来的是已经增强过了
// 2:加上BeanFactoryAware 让Bean工厂把自己暴漏出来 同时再工厂注入自己的时候
// 再触发另一个增强拦截(我最上面说过的三种回调机制中的BeanFactoryAwareMethodInterceptor)
// (疯狂套娃 我这里不说了 感兴趣的自行阅读源码了解)
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
// 不使用工厂创建
enhancer.setUseFactory(false);
// 代理类名的追加
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
// 把回调过滤器放进去 让CGLIB判断对于什么方法做怎样的增强
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}

// 内部接口 我上面说了它干什么用的了
public interface EnhancedConfiguration extends BeanFactoryAware {
}

​ 源码分析到此结束,大致流程就是标注了@Configuration的类会在容器初始化时就被修改了内容,替换成了增强子类;等到创建后执行每个@Bean方法时,每个实例会只创建一次,其他方法再调用便是从IoC容器中获取,保证了实例的单例属性。

本篇文章还有许多细节没有说清楚,读者可以自己研究或者留言评论;如果有分析错误的地方,请评论指正谢谢。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
xnnre 发布了1 篇原创文章 · 获赞 0 · 访问量 85 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: