从源码层面学习Spring中AOP代理创建过程

目录

为什么Spring中存在AOP

什么是面向切面

什么是动态代理

AOP源码解读

总结


为什么Spring中存在AOP

凡是学一样东西,要知其先后。对于Spring来说,其中2大核心IoC和AoP。对于IoC来说控制反转,对于项目中的类(bean)你直接交给Spring就行了,你想使用直接从我这里拿。当项目不断的更新迭代,需要在原有的基础上不断的增加逻辑。可能没有AoP之前,对于程序员来说可能会使用代理模式来对其解耦,此时又有啥静态代理,动态代理的。那么,对于Spring的开发人员来说,他们觉得你们项目中的类(bean)已经交给我处理了,那么他们又考虑到项目不断的更新迭代,所以干脆再造一个AoP这玩意,底层帮你们把动态代理给封装好,只需要开发者指定一下切面、切点、通知的一些映射就行,通过几个注解就能完成项目迭代的增强逻辑。

什么是面向切面

那么什么是面向切面呢?在没接触AoP之前,大家都知道Java是一个面向对象的语言,而Spring又是Java的一个框架。那么面向对象和面向切面他们又有什么瓜果呢?从官方的话来说,面向切面是对面向对象的一个延续,也是一种思想。顾名思义,切面,可以理解为在原有的代码下,我们选择一个切入点,将我们的通知(增强逻辑)给切进原有代码中。在不改变原有代码的情况下,就完成了一些功能的加强。完全的解耦,所以就流行一句面向切面编程。

切面、切点、通知的介绍。这里都是笔者自己的理解

切点:从哪里地方动刀切进去,就是说需要代理的某个方法

通知:代理增强的逻辑

切面:点成线,线程面。所以也就是由1个或者N个切点+通知组成一个面

什么是动态代理

而面向切面的实现就是基于动态代理来实现。 那么,动态代理又是啥?动...态,就可以理解为某天你的leader叫你把一段日志插入到一段代码中,你使用到代理的思想来实现,此时,就区分静态代理和动态代理,静态代理就需要你手动创建原有类的实现类然后完成增强逻辑,也可以实现,然后你的leader又叫你把其他N个类都做一下增强,那么你用静态代理来实现就需要把N个类都实现然后做增强。此时动态代理说你这样太愚蠢啦,看我的,我直接根据某种技术,直接可以生成一个他的代理类,你只需要把增强的逻辑写好,我通过某种技术生成代理类的时候,帮你把增强逻辑给放入到代理类中,不管你有多少个类需要代理,你只需要把增强逻辑写好就行,其他的交给我。

目前,动态代理的思想,在Java中就有JDK动态代理和Cglib代理。而AoP也是使用到两者做到的面向切面编程,所以想学习AoP是肯定需要有这两技术的支撑。笔者也是有考虑到,事先就把这两者的源码解读早已写好,可以通过下面链接去学习。

从源码角度解读JDK动态代理https://blog.csdn.net/qq_43799161/article/details/123536547?spm=1001.2014.3001.5501从源码角度解读Cglib动态代理https://blog.csdn.net/qq_43799161/article/details/123604338?spm=1001.2014.3001.5501

AOP源码解读

源码解读之前,做一些必备的推理和猜想,大家可以自行推理和猜想,笔者暂时能想到的猜想如下:

  1. 我们知道对于Spring中上下文刷新,会把所有解析到的Bean给创建->赋值->初始化->并且保存到缓存中。并且我们知道Aop是依赖于动态代理,而动态代理是会生成一个代理类,所以是在创建bean的时候就做其做代理,还是后续初始化的时候对其做代理然后替换原有bean呢?其实这里动态代理基础好的同学知道,产生的代理类对其做赋值只能通过反射,很麻烦,所以目前推测在原有bean赋值完以后对其做代理,并且替换原有bean。
  2. 我们知道AoP中是存在切面、切点、通知这几个概念。而Java又是面向对象的语言,一切皆对象,所以源码中肯定会对这些做抽象。并且AoP为我们提供了很多注解去完成切面、切点、通知的编写。所以在做代理之前,肯定是有解析切面、切点、通知的一些处理,解析成对应的Java类。
  3. 对于Java来说,常见的动态代理只有JDK动态代理和Cglib动态代理,那么AoP中是如何动态选择这两者做代理呢?这里我们知道JDK动态代理很局限,只能代理接口(但是跟Cglib比是偏向于轻量级),而Cglib是可以代理类和接口的,但是跟JDK动态代理比比较重量级。

笔者的推理和猜想暂时只有这么多,笔者非常建议大家发散思维,去猜测推理,最后用源码来证实自己!

看到AnnotationAwareAspectJAutoProxyCreator类,从关系图中可以清楚的看到继承了BeanPostProcessor接口,所以能明白,Spring又是靠这个扩展点在创建bean对象的时候来判断是否需要做代理。所以我们直接看到回调的时机和内容吧!

 注:这里我很多bean创建的过程跳过了,因为那都是IoC的源码,可以这么说所有Spring全家桶源码的支撑都是容器上下文refresh,建议大家一定要把这方面弄懂!

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
        
      // 查看当前类是否实现了FactoryBean,
      // 实现了就获取到FactoryBean的类型
      // 没实现就返回原有bean的名字
      Object cacheKey = getCacheKey(bean.getClass(), beanName);

      // 因为Spring存在三级缓存,并且三级缓存是来处理循环依赖
      // 为什么不是二级是三级,因为Aop的出现,所以这里就是检查缓存中是否存在已经生成好的代理对象
      // 如果已经生成了代理对象就直接删除,并且返回代理对象,如果不是就去创建
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

 大概总结一下上面流程(详细的可以看注释):

  1. bean对象反射实例化完,赋值完走到了初始化过程(也就是一些回调做增强)
  2. 然后就走beanPostProcessor的前后置处理的一个责任链模式
  3. 最后走到AbstractAutoProxyCreator类中的后置处理。
  4. 然后就是判断是否实现FactoryBean
  5. 并且判断缓存中是否存在,存在就直接返回(因为缓存中已经是代理对象)
  6. 缓存中没有就走接下来的流程
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

   // 创建代理前的判断
   // 这个是已经有此对象的代理对象了
   if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }

   // 创建代理前的判断
   // 已经创建过了
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }

   // 创建代理前的判断
   // 并且shouldSkip这个方法中会把所有的切面中的所有切点给加入到缓存中
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 根据当前bean去找到当前bean所对应的所有切点+通知,也就是Pointcut+Advisor
   // 最后会通过MethodMatcher的matches去判断当前类中是否存在一个及以上的方法需要被切面做增强
   // 并且最后将切点(Pointcut)+通知(Advisor)打包成InstantiationModelAwarePointcutAdvisorImpl
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

   // 如果没找到就是当前bean没有需要被切面做增强
   if (specificInterceptors != DO_NOT_PROXY) {

      // 假如缓存判断位
      this.advisedBeans.put(cacheKey, Boolean.TRUE);

      // 创建代理
      Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

      // 加入缓存
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   // 加入缓存,下次进来就知道不需要做代理了,就直接返回了
   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

这里就开始准备代理了,先判断是否需要被代理,再获取到当前类所对应的所有的切点+通知

shouldSkip()这个方法会对所有的切面做解析,大致流程如下:

  1. 获取到所有的bean
  2. 通过检查是否实现了@Aspect注解来证实是切面
  3. 再通过循环切面,获取到所有的方法再检查是否有@Before、@After之类的注解生成不同的切点和通知最后放入到缓存

getAdvicesAndAdvisorsForBean()方法解析出当前bean的所有切点+通知,大致流程如下:

  1. 获取到所有切面中的切点+通知
  2. 遍历当前bean中所有的方法
  3. 通过MethodMatcher的matches()方法对其方法和切点做判断
  4. 如果有一个或者多个匹配上了就返回

所以获取到当前bean的切点+通知以后,我们接下来的重心就是如何创建代理了。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
      @Nullable Object[] specificInterceptors, TargetSource targetSource) {

   if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
      AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
   }

   ProxyFactory proxyFactory = new ProxyFactory();
   proxyFactory.copyFrom(this);

   if (!proxyFactory.isProxyTargetClass()) {
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         evaluateProxyInterfaces(beanClass, proxyFactory);
      }
   }

   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
   proxyFactory.addAdvisors(advisors);
   proxyFactory.setTargetSource(targetSource);
   customizeProxyFactory(proxyFactory);

   proxyFactory.setFrozen(this.freezeProxy);
   if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
   }

   return proxyFactory.getProxy(getProxyClassLoader());
}

创建出一个ProxyFactory,顾名思义就是代理工厂,用他来创建代理类。工厂创建出物品,是肯定需要其他物品来做原材料,所以这里还把切点,被代理的bean,还有一个工厂创建的属性。不同的属性是肯定影响到工厂产出不同的物品。

所以我们接下来看AoP他是如何选择JDK代理还是Cglib代理的。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      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);
   }
}

这里先判断Optimize,也就是是否优化代理,这个默认为false。所以往下看,判断proxyTargetClass是否为true,这个基本为true,因为他的条件来自于,如果当前bean是否是与目标类一起做代理,这是肯定的。第三个条件是当前bean是否已经指定了是一个接口。

所以也就是说,如果是接口就一定是JDK动态代理,如果是类就一定用Cglib动态代理....(这确实跟没讲一样)

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
   if (logger.isTraceEnabled()) {
      logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
   }

   try {
      Class<?> rootClass = this.advised.getTargetClass();
      Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

      Class<?> proxySuperClass = rootClass;
      if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
         proxySuperClass = rootClass.getSuperclass();
         Class<?>[] additionalInterfaces = rootClass.getInterfaces();
         for (Class<?> additionalInterface : additionalInterfaces) {
            this.advised.addInterface(additionalInterface);
         }
      }

      // Validate the class, writing log messages as necessary.
      validateClassIfNecessary(proxySuperClass, classLoader);

      // Configure CGLIB Enhancer...
      Enhancer enhancer = createEnhancer();
      if (classLoader != null) {
         enhancer.setClassLoader(classLoader);
         if (classLoader instanceof SmartClassLoader &&
               ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
            enhancer.setUseCache(false);
         }
      }
      enhancer.setSuperclass(proxySuperClass);
      enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
      enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
      enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

      Callback[] callbacks = getCallbacks(rootClass);
      Class<?>[] types = new Class<?>[callbacks.length];
      for (int x = 0; x < types.length; x++) {
         types[x] = callbacks[x].getClass();
      }
      // fixedInterceptorMap only populated at this point, after getCallbacks call above
      enhancer.setCallbackFilter(new ProxyCallbackFilter(
            this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
      enhancer.setCallbackTypes(types);

      // Generate the proxy class and create a proxy instance.
      return createProxyClassAndInstance(enhancer, callbacks);
   }
   catch (CodeGenerationException | IllegalArgumentException ex) {
      throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
            ": Common causes of this problem include using a final class or a non-visible class",
            ex);
   }
   catch (Throwable ex) {
      // TargetSource.getTarget() failed
      throw new AopConfigException("Unexpected AOP exception", ex);
   }
}

上面代码是Cglib创建代理的过程,因为创建代理无非就是底层通过技术创建字节码文件,并且产生代理类的Class对象,并且通过反射创建代理实例,但是Cglib存在一个FastClass通过索引值直接执行方法(这里不过多讲,在笔者的Cglib文章中有介绍)。所以对产生代理类的过程就不过多说,想学习的可以看笔者对应的文章。

下面就用Cglib的代理来做以说明,因为JDK的动态代理比较容易。并且我们并不是关心Cglib和JDK动态代理如何操作字节码生成代理类。我们关心的是如何做的方法拦截(如何实现的增强),而Cglib和JDK动态代理中的增强方法中都用到了MethodInvocation的proceed()方法。

Callback[] mainCallbacks = new Callback[] {
      aopInterceptor,  // for normal advice
      targetInterceptor,  // invoke target without considering advice, if optimized
      new SerializableNoOp(),  // no override for methods mapped to this
      targetDispatcher, this.advisedDispatcher,
      new EqualsInterceptor(this.advised),
      new HashCodeInterceptor(this.advised)
};

这里就是Cglib的全部的方法拦截,也就是增强的回调逻辑,这些Callback方法拦截会通过底层字节码技术通过反射+ThreadLocal维护在代理类中。

我们再通过反编译字节码来反推代理方法是使用的哪一个Callback方法拦截。

 所以我们看到0号MethodInterceptor(这个是Callback的子类)对应的DynamicAdvisedInterceptor类中的intercept()方法。

@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   Object target = null;
   TargetSource targetSource = this.advised.getTargetSource();
   try {
      if (this.advised.exposeProxy) {

         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }

      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;

      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {

         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = methodProxy.invoke(target, argsToUse);
      }
      else {
         // We need to create a method invocation...
         retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
      }
      retVal = processReturnType(proxy, target, method, retVal);
      return retVal;
   }
   finally {
      if (target != null && !targetSource.isStatic()) {
         targetSource.releaseTarget(target);
      }
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

直接看到最重要的地方。

retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

这里就是对切点对应的方法做增强的逻辑实现,而具体实现,大家可以自行debug,这里大概提一嘴,内部实现使用到递归,根据解析好的通知,因为通知有环绕通知,前置通知,后置通知,异常通知,返回值通知,不同的分支肯定实现是有区别的,也就是递归的先后顺序有变换。

在Aop中,不管是Cglib动态代理还是JDK动态代理都是使用MethodInvocation的proceed()方法来对其做的拦截具体逻辑的实现。

笔者已经完善后续的处理逻辑https://blog.csdn.net/qq_43799161/article/details/124160435​​​​​​​

总结

笔者基本上把创建代理前后操作都讲的挺清楚,但是最后的方法拦截逻辑,也就是MethodInvocation的proceed()方法的执行没有去讲,因为内部使用到递归,不是三言二语能讲明白。但是笔者已经介绍完如何走到MethodInvocation的proceed()方法,所以读者可以自行debug了解底层实现。

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值