Spring底层原理分析-十(Spring代理选择)

125 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

AOP组成

  我们回顾一下在Spring中AOP的组成都有哪些,AOP既然叫面向切面编程,那么最重要的肯定就是切面,而切面由什么组成,这里我们定义一个切面。

@Aspect
class MyAspect {

    @Before("execution(* foo())")
    public void before() {
        System.out.println("before....");
    }

    @After("execution(* foo())")
    public void after() {
        System.out.println("after.....");
    }
}

  @Aspect注解就表示该类为一个切面类,切面类里面的@Before和@After注解是表明方法执行时机是发生在原始方法前还是原始方法后,而注解里面的参数就是切面的组成之一:切点。切点就是表明增强方法是用来对哪些方法进行增强,而增强方法本身也是切面的重要组成之一:通知。

AOP模拟实现

public class MyAOP {

    public static void main(String[] args) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");

        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object proceed = invocation.proceed();
            System.out.println("after....");
            return proceed;
        };
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(advisor);
        Foo proxy = (Foo) proxyFactory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
    }

    interface Foo {
        void foo();

        void bar();
    }

    static class Target implements Foo {
        @Override
        public void foo() {
            System.out.println("foo执行");
        }

        @Override
        public void bar() {
            System.out.println("bar执行");
        }
    }
}

  上面代码案例中,加了很多没有提到过的类,下面一一解析,代码最下面是提供了一个接口和一个实现类,用来做代理准备,很像之前章节提到的JDK代理方式案例。在main方法中,根据最初提到的切面组成,我们这里没有用注解的方式,而是用代码的方式模拟了各个组成结构。
  AspectJExpressionPointcut是Pointcut接口的一个实现类。实现该接口的类有很多,我们这里选用的和注解中execution表达式一致的实现类,使用该实现类创建了切入点。
  MethodInterceptor看着很是眼熟,这不就是Cglib里面的方法执行逻辑么,其实这里只是同名罢了,Cglib中使用的是Cglib提供的类,这里是AOP提供的类。实际作用两者之间确实一致的,都是为了执行增强逻辑。
  有了切点和通知逻辑,就可以组成切面,这里使用DefaultPointcutAdvisor对象将其进行组装,组装后就是一个完整的切面。
  使用ProxyFactory的方式来创建代理,将目标类和切面加入该工厂,便可获取代理类,由于我们写好了是对Foo接口的增强,这里进行强转,分别调用foo方法和bar方法。由于切点中只指定了foo方法,那么打印结果中自然不会有bar的增强。

class com.a10.MyAOP$Target$$EnhancerBySpringCGLIB$$c51b3ea7
before...
foo执行
after....
bar执行

  这里还打印了代理类的类型,根据打印结果可以看到生成的代理类是Cgbli的方式,那疑问就来了,明明采用的实现接口的方式,怎么最后使用的是Cglib代理方式呢?

ProxyFactory解读

  既然代理类是该工厂生成的,那么通过什么方式生成代理类肯定也就是它的内部的一些逻辑判断的,跟踪源码看一下具体逻辑。

public Object getProxy() {
   return createAopProxy().getProxy();
}
protected final synchronized AopProxy createAopProxy() {
   if (!this.active) {
      activate();
   }
   return getAopProxyFactory().createAopProxy(this);
}

  跟踪getProxy方法得知,代理类的生成是AopProxyFactory的createAopProxy方法创建的,继续点开查看后续逻辑。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (!NativeDetector.inNativeImage() &&
         (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) || ClassUtils.isLambdaClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

  重点就是if的这几个判断了,为true走的就是Cglib代理,为false就是JDK代理,针对这几个条件逐一进行分析。

  • !NativeDetector.inNativeImage()用于判断当前程序是否运行在GraalVM上(类似JVM),如果是着这种情况,就只能只用JDK动态代理
  • isOptimize直译为是否需要优化,可以通过设置ProxyFactory的optimize等于true来强制使用Cglib,具体的作用查阅资料好像是在较早的版本中,Cglib的效率比JDK要好,所以提供了该参数,具体原因感兴趣可以详细了解。
  • isProxyTargetClass判断是否直接代理目标类以及任何接口,如果为true将不再判断下一个条件。
  • hasNoUserSuppliedProxyInterfaces这个条件判断可以继续点进去查看源码。
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
   Class<?>[] ifcs = config.getProxiedInterfaces();
   return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
public Class<?>[] getProxiedInterfaces() {
   return ClassUtils.toClassArray(this.interfaces);
}

  interfaces参数记录了目标类实现的接口,若空那么上面的hasNoUserSuppliedProxyInterfaces条件就为true。不过这里interfaces是需要手动设置的,添加如下代码再做打印。

proxyFactory.setInterfaces(target.getClass().getInterfaces());
class com.a10.$Proxy0
before...
foo执行
after....
bar执行

  这时原来的Cglib代理就变成了JDK代理的方式。
  如果做个总结的话,那么就是当没有特定的参数限制的话(isOptimize和isProxyTargetClass默认false,不做特殊指定),Spring对代理的选择取决于有没有实现自定义的接口。