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

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容器中获取,保证了实例的单例属性。

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值