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容器中获取,保证了实例的单例属性。
本篇文章还有许多细节没有说清楚,读者可以自己研究或者留言评论;如果有分析错误的地方,请评论指正谢谢。
- 点赞
- 收藏
- 分享
- 文章举报
- Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析
- Spring5.0源码深度解析之SpringBean声明事务底层实现原理
- Spring动态加载bean后调用实现方法解析
- 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
- Spring源码学习之:@Async 方法上添加该注解实现异步调用的原理
- Spring源码学习之:@async 方法上添加该注解实现异步调用的原理
- springboot之启动原理解析及源码阅读
- Spring-boot application controller 方法自动解析Locale参数原理
- springboot之启动原理解析及源码阅读
- (二)SpringBoot源码解析--自动注入过程 Autowired原理(1)
- Spring源码学习之:@async 方法上添加该注解实现异步调用的原理
- Spring IOC原理源码解析(@Autowired原理详解 :标识属性与方法)(二 )
- Spring Boot 源码解析,一步步分析启动原理
- springboot之启动原理解析及源码阅读
- Spring5源码解析4-refresh方法之invokeBeanFactoryPostProcessors
- Android源码解析之在Activity中调用measure方法测量宽高的原理
- (一)SpringBoot源码解析----启动过程refresh()方法详解
- SpringBoot 源码解析 (一)----- SpringBoot核心原理入门
- 通过字符创调用接口中实现类的方法,SpringBean自动注入,
- 【spring源码分析】--Bean的解析与注册