前言
在前面的博文我们了解到,Spring所有的代理AopProxy
的创建最后都是ProxyCreatorSupport#createAopProxy
这个方法,而这个方法如下:
protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); }
显然它又是调用了AopProxyFactory#createAopProxy方法,它的唯一实现为DefaultAopProxyFactory。
它做了一个简单的逻辑判断:若实现类接口,使用JdkDynamicAopProxy最终去创建,否则交给ObjenesisCglibAopProxy。
最终拿到AopProxy后,调用AopProxy#getProxy()就会拿到这个代理对象,从而进行相应的工作了。
我们基本有一共共识就是:默认情况下,若我们实现了接口,就实用JDK动态代理,若没有就实用CGLIB。那么就下来,就具体看看关乎到代理对象的创建、执行的一个具体过程原理
常识普及
AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。
AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。
静态代理
静态代理是编译阶段生成AOP代理类,也就是说生成的字节码就织入了增强后的AOP对象;(并不会创建出多余的对象)
实现方式:
- 包装器模式:持有目标对象的引用,然后实际上是调用目标对象的方法。 这种方式也可称为代理模式,但是有明显的缺点(比如一般都需要实现同一个接口,且它是以编码的方式去实现的,侵入性高)
- AspectJ静态代理方式:非常非常强大。Aspectj并不是动态的在运行时生成代理类,而是在编译的时候就植入代码到class文件。由于是静态织入的,所以性能相对来说比较好。Aspectj不受类的特殊限制,不管方法是private、或者static、或者final的,都可以代理,Aspectj不会代理除了限定方法之外任何其他诸如toString(),clone()等方法,唯一缺点就是必须有AspectJ自己的编译器的支持,所以其实很少使用 Spring也是提供了相关类支持的,比如:LoadTimeWeaverAwareProcessor
基于AspectJ的静态代理方式非常强大,但是它依赖于它自己的编译器。并且还有自己的个性化语言,使用起来不够方便,因此其实还是使用得较少的。主要还是以动态代理为主~~~
动态代理
动态代理则不会修改字节码,而是在内存中临时生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
这在我们平时使用中得到了大量的使用,因为使用简单并且还非常灵活,下面就重点介绍。
AopProxy:Aop代理接口
它是一个AOP代理的抽象接口。提供了两个方法,让我们可以获取对应 配置的AOP对象的代理:
public interface AopProxy { //Create a new proxy object. Uses the AopProxy's default class loader ClassUtils.getDefaultClassLoader() Object getProxy(); Object getProxy(@Nullable ClassLoader classLoader); }
它的继承关系也很简单,就是接下来我们要说的那几个
环境构建
为了更好的做源码分析,因此此处我构建一个非常简单的Spring AOP的环境,来跟踪一下它的来龙去脉
public class Main { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(new Demo()); proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1))); DemoInterface demo = (DemoInterface) proxyFactory.getProxy(); //你被拦截了:方法名为:hello 参数为--[] //this demo show demo.hello(); System.out.println(proxyFactory.getTargetClass()); //class com.fsx.maintest.Demo System.out.println(proxyFactory.getTargetSource()); //SingletonTargetSource for target object [com.fsx.maintest.Demo@643b1d11] System.out.println(Arrays.asList(proxyFactory.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface] System.out.println(Arrays.asList(proxyFactory.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]] // 获取类型,看看是JDK代理还是cglib的 System.out.println(demo instanceof Proxy); //true 所有的JDK代理都是继承自Proxy的 System.out.println(demo instanceof SpringProxy); //true System.out.println(demo.getClass()); //class com.fsx.maintest.$Proxy0 System.out.println(Proxy.isProxyClass(demo.getClass())); //true System.out.println(AopUtils.isCglibProxy(demo)); //false //测试Advised接口、DecoratingProxy的内容 Advised advised = (Advised) demo; System.out.println(Arrays.asList(advised.getProxiedInterfaces())); //[interface com.fsx.maintest.DemoInterface] System.out.println(Arrays.asList(advised.getAdvisors())); //[org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.fsx.maintest.Main$$Lambda$2/1349414238@2ef5e5e3]] System.out.println(advised.isExposeProxy()); //false System.out.println(advised.isFrozen()); //false //System.out.println(advised.removeAdvice(null)); DecoratingProxy decoratingProxy = (DecoratingProxy) demo; System.out.println(decoratingProxy.getDecoratedClass()); //class com.fsx.maintest.Demo System.out.println("-----------------------------------------------------"); // Object的方法 ==== 所有的Object方法都不会被AOP代理 这点需要注意 System.out.println(demo.equals(new Object())); System.out.println(demo.hashCode()); System.out.println(demo.getClass()); // 其余方法都没被拦截 只有toString()被拦截了 咋回事呢?它也不符合切点表达式的要求啊 看下面的解释吧 // 你被拦截了:方法名为:hello 参数为--[] // com.fsx.maintest.Demo@643b1d11 System.out.println(demo.toString()); } } interface DemoInterface { void hello(); } class Demo implements DemoInterface { @Override public void hello() { System.out.println("this demo show"); } }
因为本文只讲述代理的创建,所以使用最为简单的ProxyFactory来创建代理,排除其它的干扰因素
JdkDynamicAopProxy
如上搭建的环境,生成的就是JDK的动态代理对象。这里面逻辑比较干净简单了:
1.setTarget和setInterfaces交给ProxyCreatorSupport
2.addAdvice:此处就是个Advice前置通知增强器。最终会被包装成DefaultPointcutAdvisor交给ProxyCreatorSupport
3.getProxy()才是今天的关键,他内部会去new出来一个JdkDynamicAopProxy,然后调用其getProx()获取到动态代理出来的对象
那么接下来,就深入它的源码看看究竟:
// 我们发现它自己就实现了了InvocationHandler,所以处理器就是它自己。会实现invoke方法 // 它还是个final类 默认是包的访问权限 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** 这里就保存这个AOP代理所有的配置信息 包括所有的增强器等等 */ private final AdvisedSupport advised; // 标记equals方法和hashCode方法是否定义在了接口上===== private boolean equalsDefined; private boolean hashCodeDefined; public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); // 内部再校验一次:必须有至少一个增强器 和 目标实例才行 if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { throw new AopConfigException("No advisors and no TargetSource specified"); } this.advised = config; } @Override public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } // 真正创建JDK动态代理实例的地方 @Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } // 这部很重要,就是去找接口 我们看到最终代理的接口就是这里返回的所有接口们(除了我们自己的接口,还有Spring默认的一些接口) 大致过程如下: //1、获取目标对象自己实现的接口们(最终肯定都会被代理的) //2、是否添加`SpringProxy`这个接口:目标对象实现对就不添加了,没实现过就添加true //3、是否新增`Adviced`接口,注意不是Advice通知接口。 实现过就不实现了,没实现过并且advised.isOpaque()=false就添加(默认是会添加的) //4、是否新增DecoratingProxy接口。传入的参数decoratingProxy为true,并且没实现过就添加(显然这里,首次进来是会添加的) //5、代理类的接口一共是目标对象的接口+上面三个接口SpringProxy、Advised、DecoratingProxy(SpringProxy是个标记接口而已,其余的接口都有对应的方法的) //DecoratingProxy 这个接口Spring4.3后才提供 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); // 第三个参数传的this,处理器就是自己嘛 到此一个代理对象就此new出来啦 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } // 找找看看接口里有没有自己定义equals方法和hashCode方法,这个很重要 然后标记一下 // 注意此处用的是getDeclaredMethods,只会找自己的 private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) { for (Class<?> proxiedInterface : proxiedInterfaces) { Method[] methods = proxiedInterface.getDeclaredMethods(); for (Method method : methods) { if (AopUtils.isEqualsMethod(method)) { this.equalsDefined = true; } if (AopUtils.isHashCodeMethod(method)) { this.hashCodeDefined = true; } // 小技巧:两个都找到了 就没必要继续循环勒 if (this.equalsDefined && this.hashCodeDefined) { return; } } } } // 对于这部分代码和采用CGLIB的大部分逻辑都是一样的,Spring对此的解释很有意思: // 本来是可以抽取出来的,使得代码看起来更优雅。但是因为此会带来10%得性能损耗,所以Spring最终采用了粘贴复制的方式各用一份 // Spring说它提供了基础的套件,来保证两个的执行行为是一致的。 //proxy:指的是我们所代理的那个真实的对象;method:指的是我们所代理的那个真实对象的某个方法的Method对象args:指的是调用那个真实对象方法的参数。 // 此处重点分析一下此方法,这样在CGLIB的时候,就可以一带而过了~~~因为大致逻辑是一样的 @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 它是org.aopalliance.intercept这个包下的 AOP联盟得标准接口 MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; // 进入invoke方法后,最终操作的是targetSource对象 // 因为InvocationHandler持久的就是targetSource,最终通过getTarget拿到目标对象 TargetSource targetSource = this.advised.targetSource; Object target = null; try { //“通常情况”Spring AOP不会对equals、hashCode方法进行拦截增强,所以此处做了处理 // equalsDefined为false(表示自己没有定义过eequals方法) 那就交给代理去比较 // hashCode同理,只要你自己没有实现过此方法,那就交给代理吧 // 需要注意的是:这里统一指的是,如果接口上有此方法,但是你自己并没有实现equals和hashCode方法,那就走AOP这里的实现 // 如国接口上没有定义此方法,只是实现类里自己@Override了HashCode,那是无效的,就是普通执行吧 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { return hashCode(); } // 下面两段做了很有意思的处理:DecoratingProxy的方法和Advised接口的方法 都是是最终调用了config,也就是this.advised去执行的~~~~ else if (method.getDeclaringClass() == DecoratingProxy.class) { // There is only getDecoratedClass() declared -> dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } // 这个是最终该方法的返回值~~~~ Object retVal; //是否暴露代理对象,默认false可配置为true,如果暴露就意味着允许在线程内共享代理对象, //注意这是在线程内,也就是说同一线程的任意地方都能通过AopContext获取该代理对象,这应该算是比较高级一点的用法了。 // 这里缓存一份代理对象在oldProxy里~~~后面有用 if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //通过目标源获取目标对象 (此处Spring建议获取目标对象靠后获取 而不是放在上面) target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // 获取作用在这个方法上的所有拦截器链~~~ 参见DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice方法 // 会根据切点表达式去匹配这个方法。因此其实每个方法都会进入这里,只是有很多方法得chain事Empty而已 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { // 若拦截器为空,那就直接调用目标方法了 // 对参数进行适配:主要处理一些数组类型的参数,看是表示一个参数 还是表示多个参数(可变参数最终到此都是数组类型,所以最好是需要一次适配) Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); // 这句代码的意思是直接调用目标方法~~~ retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // 创建一个invocation ,此处为ReflectiveMethodInvocation 最终是通过它,去执行前置加强、后置加强等等逻辑 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // 此处会执行所有的拦截器链 交给AOP联盟的MethodInvocation去处理。当然实现还是我们Spring得ReflectiveMethodInvocation retVal = invocation.proceed(); } // 获取返回值的类型 Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // 一些列的判断条件,如果返回值不为空,且为目标对象的话,就直接将目标对象赋值给retVal retVal = proxy; } // 返回null,并且还不是Void类型。。。抛错 else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException( "Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { // 释放~~ if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } // 把老的代理对象重新set进去~~~ if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } } // AOP帮我们实现的CgLib方法 @Override public boolean equals(@Nullable Object other) { if (other == this) { return true; } if (other == null) { return false; } JdkDynamicAopProxy otherProxy; if (other instanceof JdkDynamicAopProxy) { otherProxy = (JdkDynamicAopProxy) other; } else if (Proxy.isProxyClass(other.getClass())) { InvocationHandler ih = Proxy.getInvocationHandler(other); if (!(ih instanceof JdkDynamicAopProxy)) { return false; } otherProxy = (JdkDynamicAopProxy) ih; } else { // Not a valid comparison... return false; } // If we get here, otherProxy is the other AopProxy. return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised); } // AOP帮我们实现的HashCode方法 @Override public int hashCode() { return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); } }
细节:
- 除了实现类里自己写的方法(接口上没有的),其余方法统一都会进入代理得invoke()方法里面。只是invoke上做了很多特殊处理,比如DecoratingProxy和Advised等等的方法,都是直接执行了。
- object的方法中,toString()方法会被增强(至于为何,我至今还没找到原因,麻烦的知道的给个答案) 因为我始终不知道AdvisedSupport#methodCache这个字段事什么把toString()方法缓存上的,打断点都没跟踪上
- 生成出来的代理对象,Spring默认都给你实现了接口:SpringProxy、DecoratingProxy、Advised- 说明:CGLIB代理出来的对象没有实现接口DecoratingProxy。(多谢评论区【神的力量】小伙伴的指正)