关于Spring的两三事:代理对象的生成时机

2022-11-08 09:23:37 浏览数 (1)

“人生苦短,不如养狗 作者:Brucebat.Sun ”

一、前言

  前段时间在翻阅博客时遇到了这样一个问题:在存在代理对象的情况下,BeanPostProcessor处理的到底是目标对象还是代理对象?仔细思考了一下,其实这个问题的本质是想要考察代理对象的生成时机。

  在之前的文章中我们曾经聊过Spring框架有两大核心,分别是IoC容器AOP模块,其中后者能力的提供会依赖于前者。也就是说AOP模块代理对象的生成也是由IoC容器来管理控制的,即答案就在Bean的创建过程中

“以下的讨论和代码分析均基于SpringBoot 2.4.4版本 ”

二、浅析Bean的创建过程

  为了让大家能够更加简明地了解到Bean的创建过程,笔者基于源码(主要是基于AbstractAutowireCapableBeanFactory#createBean方法)绘制了如下关于Bean生命周期的流程图。

  从上图中可以看到,在Bean的创建过程中除了耳熟能详的“实例创建—属性赋值—初始化”三个阶段,在实际创建Bean之前还有一些预处理过程。而在整个Bean的创建流程当中,预处理阶段初始化阶段会进行代理对象的生成。

  这里我们具体看一下处理阶段中用于提前生成代理对象的代码:

代码语言:javascript复制
try {
   // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
    // 给BeanPostProcessor一个机会提前返回一个代理对象而不是创建目标对象
   Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
   if (bean != null) {
    return bean;
   }
  }
  catch (Throwable ex) {
   throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
     "BeanPostProcessor before instantiation of bean failed", ex);
  }

...
  
# resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)
  
    Object bean = null;
  if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
   // Make sure bean class is actually resolved at this point.
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    Class<?> targetType = determineTargetType(beanName, mbd);
    if (targetType != null) {
     bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
     if (bean != null) {
      bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
     }
    }
   }
   mbd.beforeInstantiationResolved = (bean != null);
  }
  return bean;

  从上面的源码当中可以发现,在预处理阶段是通过BeanPostProcessor#postProcessBeforeInstantiation方法来生成对应的代理对象。经过进一步的Debug,我们会发现这一阶段实际上是通过AbstractAutoProxyCreator来生成代理对象的。不过通过Debug笔者发现,在实际的项目启动过程中,不存在需要在预处理阶段生成代理对象的场景(笔者在引入数据库和Dubbo服务等常见工具后依然没有发现有需要的情况,如果有找到存在的场景使用可以私信告诉笔者)。而我们日常开发使用的AOP处理方式其所需的代理对象都是通过Bean初始化阶段BeanPostPorcessor#postProcessAfterInitialization方法创建的,这里也是由相同的BeanPostProcessor实现类AbstracAutoProxyCreator来进行代理对象生成的。

  至此,我们已经弄清楚了代理对象的生成时机,下面让我们围绕AbstractAutoProxyCreator来具体分析一下代理对象的生成逻辑。

三、代理对象的生成

  在上面关于Bean创建过程的分析中我们已经了解到代理对象的生成实际上是靠BeanPostProcessor的实现类AbstractAutoProxyCreator来完成的,其方法入口为postProcessBeforeInstantiationpostProcessAfterInitialization。前者在上文中有提到正常启动过程基本不会用到该方法中创建代理对象的逻辑,在源码中的注释也有提到:

“Create proxy here if we have a custom TargetSource. Suppresses unnecessary default instantiation of the target bean: The TargetSource will handle target instances in a custom fashion. ”

  这里实际上提供给有自定义目标对象实例的情况一个机会进行代理对象的创建,通过此处代理对象的创建可以抑制不必要的实例化,将目标实例的处理权全部交由用户自定义。

  而实际我们使用AOP模块时所需的代理对象则是由postProcessAfterInitialization方法来创建的。具体创建流程如下:

  在上图中ProxyCreatorSupport#createAopProxy的位置(实际内部是调用了DefaultAopProxyFactory#createAopProxy方法)我们看到了两个非常熟悉的身影——JDK动态代理JdkDynamicAopProxy和CGlib代理ObjenesisCglibAopProxy两种代理对象生成类。由于网络上已经有非常多的文章对这两种动态代理方式做过分析和讲解,这里就不进行展开讨论了。我们可以简单看一下Spring中是如何判断该使用何种动态代理方式进行代理对象生成的:

代码语言:javascript复制
// DefaultAopProxyFactory#createAopProxy

if (!NativeDetector.inNativeImage() &&
    (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
  // 判断镜像不处于构建或者运行中,并且应该优化或者直接代理目标类以及任何接口或者指定的AdvisedSupport只指定了SpringProxy接口或者没有指定接口
   Class<?> targetClass = config.getTargetClass();
   if (targetClass == null) {
    throw new AopConfigException("TargetSource cannot determine target class: "  
      "Either an interface or a target is required for proxy creation.");
   }
   if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        // 判断目标类是否为接口或者目标类是否为代理类
    return new JdkDynamicAopProxy(config);
   }
   return new ObjenesisCglibAopProxy(config);
  }
  else {
   return new JdkDynamicAopProxy(config);
  }

 从上面的代码中我们可以验证另一个经常在其他文章中看到的理论,Spring默认的动态代理方式是JDK动态代理,因为在上面不满足任何限制条件的情况下,最终返回的是JdKDynamicAopProxy实例。

四、总结

  在解决了代理对象生成时机以及具体的生成逻辑这个问题之后,我们再回头看一下引发这个问题的源头:在存在代理对象的情况下,BeanPostProcessor处理的到底是目标对象还是代理对象?其实这个问题在上面的流程图中就可以得到解释,BeanPostProcessor处理的一定是目标对象。当然,我们从代理对象的目的来分析也能得出同样的答案,无论是通过JDK动态代理方式还是使用CGlib方式生成代理对象,其本质目的是为了对被代理的目标对象的某些方面进行增强处理。如果BeanPostProcessor的处理逻辑(一般为初始化操作或一些Bean信息的读取)作用于代理对象之上,那么就会破坏代理对象设计的本意。

  从上一篇关于Bean加载顺序的分析以及这一篇关于代理对象生成时机的讨论不难发现,其实Spring框架中的很多问题都是围绕IoC容器在进行的,所以只要能耐心地将Bean创建部分的源码阅读消化完毕,很多实际工作或者面试中遇到的问题基本都能得到解答。(此处不得不吐槽,之所以将耐心重点圈出主要是因为Spring源码看起来确实会有点费劲,毕竟面向抽象编程学得太好,不是一点点Debug可能都找不到流程跑到哪里去了)

  最后,疫情仍在继续,希望大家出行注意防护,身体健康,每天保持好心情~~

0 人点赞