Spring中眼见为虚的 @Configuration 配置类

有道无术,术尚可求也!有术无道,止于术!

1、前言

在这里我不得不感慨Spring的代码的完善与优秀,从以前看源码迷迷糊糊到如今基本了解Spring的部分源码后,越来越发现Spring开发者的思虑之周全!java

以前说过学习源码的目的在哪?正如我特别喜欢的一句话,有道无术,术尚可求也!有术无道,止于术!,对于Spring的了解仅仅局限于使用远远不够,Spring做为一个国内绝大多数java开发者使用的一个项目管理框架,他是一个生态,什么是生态?好比如今的SpringBootSpringCloud,他们是什么?是Spring生态中的一个组成部分!他们利用Spring生态中提供的各类扩展点,一步一步的封装,成就了如今Spring快速启动自动配置等亮眼的功能!做为Spring的使用者,咱们理应了解Spring的实现和各类扩展点,从而可以真正的深刻Spring生态!深刻了,再去研究生态中的组成部分如:SpringBoot之流的框架,也就水到渠成了!web

2、开篇一问

相信大部分开发者对于Spring的使用都是水到渠成的!那么下面一段代码你们必定很熟悉!面试

/**
 * 全局配置类
 *
 * @author huangfu
 */

@Configuration
public class ExpandRunConfig {
 @Bean
 public TestService testService() {
  return new TestServiceImpl();
 }

 @Bean
 public UserService userService() {
  testService();
  return new UserServiceImpl();
    }
}

能够很清楚的看到,这里交给Spring管理了两个类TestService,UserService,可是在userService()里面又引用了testService()! 那么问题来了,你以为TestService会被实例化几回?spring

相信有很多同窗,张口就说一次,对,没错,可是为何呢?我当时对这里的问题深深的感到自我怀疑!甚至一度怀疑本身的java基础,明明这里调用了另一个方法,可是为何没有进行两次实例化呢?微信

我问了不少同事、朋友,他们只知道这样写是没有问题的!可是具体缘由不知道!为何呢?咱们带着这个问题往下看!app

3、你看到的配置类是真的配置类吗?

咱们从bean容器里面把这个配置类取出来,看一下有什么不同!框架

public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ExpandRunConfig.class);
    ExpandRunConfig bean = ac.getBean(ExpandRunConfig.class);
    System.out.println(bean);

}

咱们debug看一下,咱们取出来了个什么玩意!编辑器

被代理的Spring配置类

果真,他不是他了,他被(玷污)代理了,并且使用的代理是cglib,那么这里就能够猜想一个问题,在Bean方法中调用另一个Bean方法,他必定是经过代理来作的,从而完成了屡次调用只实例化一次的功能!ide

到这里,解决了,原来是这样!那么如今有两个疑问:post

  1. 何时给配置类加的代理?
  2. 代理逻辑里面是怎么完成屡次调用返回同一个实例的功能的?

下面咱们就带着两个疑问,去追一下Spring源码,看看究竟是如何进行的!

4、代理图示

cglib代理配置类的流程图

这张图我放出来,若是你没有了解过的话,必定是很迷惑,不要紧,后面会用源码解释,并且源码看完以后,咱们会大概手写一个,帮助你理解!

5、源码详解

不妨猜一下,看过我之前的文章的读者都应该了解!Spring建立bean实例的时候,所须要的信息是在beanDefinitionMap里面存放的,那么在初始化的时候解析bean的bd的时候,必定是替换了配置类bd里面的类对象,才会使后面实例化config的时候变成了一个代理对象,因此咱们的入口应该在这里:

invokerBeanFactory入口方法

那么这里面的代码是在哪加强的呢?

/**
  * 准备配置类以在运行时为Bean请求提供服务
  * 经过用CGLIB加强的子类替换它们。
  */

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    ..................忽略对应的逻辑................
    //字节码加强配置类  貌似用的cglib
    enhanceConfigurationClasses(beanFactory);
    ..................忽略对应的逻辑................
}

调用配置类的加强逻辑  enhanceConfigurationClasses

/**
 * 对BeanFactory进行后处理以搜索配置类BeanDefinitions; 而后,任何候选人都将经过{@link ConfigurationClassEnhancer}.
 * 候选状态由BeanDefinition属性元数据肯定。
 * @see ConfigurationClassEnhancer
 */

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    // 最终须要作加强的Bean定义们
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        //什么是Full类,简单来讲就是加了 @Configuration 的配置类
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
           .....忽略日志打印......
            //// 若是是Full模式,才会放进来
            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
        }
    }
    if (configBeanDefs.isEmpty()) {
        // 没有什么可加强的->当即返回
        return;
    }
    //配置类加强器
    // ConfigurationClassEnhancer就是对配置类作加强操做的核心类
    //初始化会初始化两个chlib拦截类  BeanFactoryAwareMethodInterceptor 和  BeanMethodInterceptor
    //这个是重点  这个类里面的方法会产生最终的代理类
    //这个方法里面有个
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    //对每一个Full模式的配置类,一个个作enhance()加强处理
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
        AbstractBeanDefinition beanDef = entry.getValue();
        // 若是@Configuration类被代理,请始终代理目标类
        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        try {
            // 设置用户指定的bean类的加强子类
            //CGLIB是给父类生成子类对象的方式实现代理,因此这里指定“父类”类型
            Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
            if (configClass != null) {
                //作加强处理,返回enhancedClass就是一个加强过的子类
                //这个是重点,这个会构建一个cglib的加强器,最终返回被代理完成的类对象!
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                //不相等,证实代理成功,那就把实际类型设置进去
                if (configClass != enhancedClass) {
                    ..... 忽略日志打印 ....
                    //这样后面实例化配置类的实例时,实际实例化的就是加强子类喽
                    //这里就是替换 config类的beanClass对象的!
                    beanDef.setBeanClass(enhancedClass);
                }
            }
        }
        catch (Throwable ex) {
            。。。。。忽略异常处理。。。。。。。
        }
    }
}

这个类相当重要,总共作了这样几件事:

  1. 筛选配置类,只有加了 @Configuration的配置类才会被加强!
  2. 使用 enhancer.enhance构建一个加强器,返回加强后的代理类对象!
  3. 替换配置类原始的beanClass,为代理后的class!

那么,咱们最关心的是如何实现的,确定要看enhancer.enhance里面的逻辑~

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
  // 若是已经实现了该接口,证实已经被代理过了,直接返回
  if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
   。。。。忽略日志打印。。。。
   return configClass;
  }
  //没被代理过。就先调用newEnhancer()方法建立一个加强器Enhancer
  //而后在使用这个加强器,生成代理类字节码Class对象
  //建立一个新的CGLIB Enhancer实例,而且作好相应配置
        //createClass是设置一组回调(也就是cglib的方法拦截器)
  Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
  if (logger.isTraceEnabled()) {
   。。。。忽略日志打印。。。。
  }
  return enhancedClass;
 }

这是一个过分方法,真正去构建一个代理加强器的是newEnhancer方法,咱们彷佛接近了咱们要的答案!

/**
  * 建立一个新的CGLIB {@link Enhancer}实例。
  */

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    // 目标类型:会以这个做为父类型来生成字节码子类
    enhancer.setSuperclass(configSuperClass);
    //代理类实现EnhancedConfiguration接口,这个接口继承了BeanFactoryAware接口
    //这一步颇有必要,使得配置类强制实现 EnhancedConfiguration即BeanFactoryAware 这样就能够轻松的获取到beanFactory
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    // 设置生成的代理类不实现org.springframework.cglib.proxy.Factory接口
    enhancer.setUseFactory(false);
    //设置代理类名称的生成策略:Spring定义的一个生成策略 你名称中会有“BySpringCGLIB”字样
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    //设置拦截器/过滤器  过滤器里面有一组回调类,也就是真正的方法拦截实例
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

若是你熟悉cglib的话,确定对这几行代码熟悉无比,主要作了这样几件事!

  1. 设置须要代理的类
  2. 设置生成的代理类须要实现的接口,这里设置实现了 EnhancedConfiguration,注意这个是一个很骚的操做,他是可以保证最终类可以从beanFactory返回的一个重要逻辑,为何?由于 EnhancedConfigurationBeanFactoryAware的子类,Spring会回调他,给他设置一个 beanFactory ,若是你看不懂不妨先把和这个记下来,等看完在回来仔细品味一下!
  3. 设置过滤器,过滤器里面实际上是一组回调方法,这个回调方法是最终方法被拦截后执行的真正逻辑,咱们一会要分析的也是过滤器里面这一组回调实例!
  4. 返回最终的加强器!

刚刚也说了,咱们须要重点关注的是这一组拦截方法,咱们进入到拦截器里面,找到对应的回调实例!

CALLBACK_FILTER:常量对应的是一个过滤器,咱们看它如何实现的:

private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

那么此时 CALLBACKS 就是我么要找的回调方法,点进去能够看到:

// 要使用的回调。请注意,这些回调必须是无状态的。
private static final Callback[] CALLBACKS = new Callback[] {
    //这个是真正可以Bean方法屡次调用返回的是一个bean实例的实际拦截方法,这个拦截器就是彻底可以说明,为何屡次调用只返回
    //一个实例的问题
    new BeanMethodInterceptor(),
    //拦截 BeanFactoryAware 为里面的 setBeanFactory 赋值
    //刚刚也说了,加强类会最终实现 BeanFactoryAware 接口,这里就是拦截他的回调方法 setBeanFactory方法,获取bean工厂!
    new BeanFactoryAwareMethodInterceptor(),
    //这个说实话  真魔幻  我本身实现cglib的时候一直在报错  报一个本身抛出的异常,异常缘由是没有处理object里面的eques等
    //方法,这个就是为了处理那些没有被拦截的方法的实例  这个些方法直接放行
    //这个实例里面没有实现任何的东西,空的,表明着不处理!
    NoOp.INSTANCE
};

具体里面每个拦截器到底是干吗的,注释说的很明白,咱们从第二个提及!为何不从第一个呢?第一个比较麻烦,咱们由浅入深,逐步的说!

BeanFactoryAwareMethodInterceptor

/**
  * 拦截对任何{@link BeanFactoryAware#setBeanFactory(BeanFactory)}的调用 {@code @Configuration}类实例,用于记录{@link BeanFactory}。
  * @see EnhancedConfiguration
  */

private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptorConditionalCallback {

    @Override
    @Nullable
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //找到本类(代理类)里名为`$$beanFactory`的字段
        Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
        //若没找到直接报错。若找到了此字段,就给此字段赋值
        Assert.state(field != null"Unable to find generated BeanFactory field");
        field.set(obj, args[0]);

        // 实际的(非CGLIB)超类是否实现BeanFactoryAware?
        // 若是是这样,请调用其setBeanFactory()方法。若是没有,请退出。
        //若是用户类(也就是你本身定义的类)本身实现了该接口,那么别担忧,也会给你赋值上
        if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
            return proxy.invokeSuper(obj, args);
        }
        return null;
    }

    /**
  * 执行到setBeanFactory(xxx)方法时匹配成功
  * @param candidateMethod 当前执行的方法
  * @return
  */

    @Override
    public boolean isMatch(Method candidateMethod) {
        //判断方法是否是 `setBeanFactory` 方法 
        return isSetBeanFactory(candidateMethod);
    }
    
    .........忽略没必要要逻辑.........
}

不知道你注意没有,在最终生成的代理配置类里面有一个 $$beanFactory属性,这个属性就是在这里被赋值的!再把图片放出来,看最后一个属性!

被代理的Spring配置类

这个拦截器的主要做用:

  1. 拦截 setBeanFactory方法,为 $$beanFactory赋值!

好了,这个拦截器介绍完了,功能你们也记住了,那么,咱们分析下一个拦截器,这个是重点!

BeanMethodInterceptor

/**
 * 加强{@link Bean @Bean}方法以检查提供的BeanFactory中的 这个bean对象的存在。
 * @throws Throwable 做为全部在调用时可能引起的异常的统筹 代理方法的超级实现,即实际的{@code @Bean}方法
 * 当该方法通过匹配成功后 会进入到这个拦截方法  这个是解决bean方法只被建立一次的重要逻辑
 */

@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                        MethodProxy cglibMethodProxy)
 throws Throwable 
{
    //经过反射,获取到Bean工厂。也就是 $$beanFactory 这个属性的值
    //也就是上一个拦截器被注入的值
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    //拿到Bean的名称
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

    // 肯定此bean是否为做用域代理
    //方法头上是否标注有@Scoped注解
    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }
    。。。。。。忽略与本题无关的代码。。。。。。。。。。
        
    // 检查给定的方法是否与当前调用的容器相对应工厂方法。
    // 比较方法名称和参数列表来肯定是不是同一个方法
    // 怎么理解这句话,参照下面详解吧
    //在整个方法里面,我认为这个判断是核心,为何说他是核心,由于只有这个判断返回的是false的时候他才会真正的走加强的逻辑
    //何时会是false呢?
    //首先  spring会获取到当前使用的方法   其次会获取当前调用的方法,当两个方法不一致的时候会返回false
    //什么状况下胡不一致呢?
    //当在bean方法里面调用了另外一个方法,此时当前方法和调用方法不一致,致使返回课false而后去执行的加强逻辑
    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        // 这是个小细节:若你@Bean返回的是BeanFactoryPostProcessor类型
        // 请你使用static静态方法,不然会打印这句日志的~~~~
        // 由于若是是非静态方法,部分后置处理失效处理不到你,可能对你程序有影像
        // 固然也可能没影响,因此官方也只是建议而已~~~
        if (logger.isInfoEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            ...... 忽略日志打印......
        }
        // 这表示:当前方法,就是这个被拦截的方法,那就没啥好说的
        // 至关于在代理代理类里执行了super(xxx);
        // 可是,可是,可是,此时的this依旧是代理类
        //这个事实上上调用的是自己的方法  最终会再次被调用到下面的 resolveBeanReference 方法
        //这里的设计很奇妙  为何这么说呢?
        //了解这个方法首先要对cglib有一个基础的认识 为何这么说呗?
        //首先要明白 cglib是基于子类集成的方式去加强的目标方法的
        //因此在不进行加强的时候就能够以很轻松的调用父类的原始方法去执行实现
        //当前调用的方法和调用的方法是一个方法的时候  就直接调用cglib父类  也就是原始类的建立方法直接建立
        //当不同的时候  会进入到下面的方法  直接由beanFactory返回  精妙!!
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }
    //方法里调用的实例化方法会交给这里来执行
    //这一步的执行是真正的执行方式,当发现该方法须要代理的时候不调用父类的原始方法
    //而是调用我须要代理的逻辑去返回一个对象,从而完成对对象的代理
    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

乍一看,是否是好多,没事咱们一点一点分析:

  1. 首先我么看那个判断 if (isCurrentlyInvokedFactoryMethod(beanMethod))这个判断是很重要的!他就是从 ThreadLocal里面取出本次调用的工厂方法,前面提到过不少次工厂方法,什么是工厂方法?就是你写的那个@Bean对应的方法,咱们就叫作工厂方法,咱们以上面 开篇一问里的那个代码为例!
    • 当建立 UserServiceImpl的时候,会先存储当前的方法对象也就是 UserServiceImpl的方法对象,也就是放置到 ThreadLocal里面去!
    • 而后发现是一个代理对象,进入到代理逻辑,在代理逻辑里面,走到这个判断逻辑,发现本次拦截的方法和 ThreadLocal里面的方法是一致的,而后就放行,开始调用真正的 userService()方法,执行这个方法的时候,方法内部调用了 testService();方法!
    • 发现 testService()又是一个代理对象,因而又走代理逻辑,而后走到这个判断,判断发现当前拦截的方法是 testService而ThreadLocal里面的方法倒是 userService,此时判断就失败了,因而就走到另一个分支!
    • 另一个分支就再也不执行这个方法了,而是直接去beanFactory去取这个bean,直接返回!
  2. return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);这个是当拦截的方法是工厂方法的时候直接放行,执行父类的逻辑,为何是父类!Cglib是基于继承来实现的,他的父类就是原始的那个没有通过代理的方法,至关于调用 super.userService()去调用原始逻辑!
  3. resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);这个也是一会咱们要看的代码逻辑,这个就是当判断不成立,也就是发现工厂方法里面还调用了另一个工厂方法的时候,会进入到这里面!那咱们看一下这里面的逻辑吧!

resolveBeanReference方法逻辑

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
                                    ConfigurableBeanFactory beanFactory, String beanName)
 
{
  。。。。。。。。。忽略没必要要代码。。。。。。。。。
        //经过getBean从容器中拿到这个实例
        //这个beanFactory是哪里来的,就是第一个拦截器里面注入的`$$beanFactory`
        Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
                               beanFactory.getBean(beanName));

        。。。。。。。。。忽略没必要要代码。。。。。。。。。
        return beanInstance;
    }
 
}

这里面的主要逻辑就是从beanFactory里面获取这个方法对应的bean对象,直接返回!而不是再去调用对应的方法建立!这也就是为何屡次调用,返回的实例永远只是一个的缘由!

6、总结

整个过程比较绕,读者能够本身跟着文章调试一下源码,相信通过过深度思考,你必定有所收获!

整个过程分为两大部分:

  1. 加强配置类
    • 检测加了 @Configuration注解的配置类!
    • 建立代理对象(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor)做为加强器的回调方法!
    • 返回代理后的类对象!
    • 设置进配置类的beanClass!
  2. 建立bean
    • 一致的话就走原始的建立逻辑!
    • 不一致,就从bean工厂获取!
    • 发现该bean建立的时候依附配置类(也就是加了@Bean的方法)!
    • 回调加强配置类的方法,并记录该方法!
    • 判断拦截的方法和记录的方法是否一致
    • 返回建立好的bean

收工!

推荐阅读:

  1. 据说你一读Spring源码就懵?我帮你把架子搭好了,你填就行!
  2. 万字长文,助你深度遨游Spring循环依赖源码实现!
  3. 生气!面试官你过来,我给你手写一个Spring Aop实现!

才疏学浅,若是文章中理解有误,欢迎大佬们私聊指正!欢迎关注做者的公众号,一块儿进步,一块儿学习!



       
❤️「转发」 「在看」 ,是对我最大的支持❤️



本文分享自微信公众号 - JAVA程序狗(javacxg)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索