AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
- 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
- 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
- 作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
- aspectj 在编译和加载时,修改目标字节码,性能较高
- aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
- 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
ajc 编译器
- 编译器也能修改 class 实现增强
- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
注意
- 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
- 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
agent 类加载
- 类加载时可以通过 agent 修改 class 实现增强
AOP 实现之 proxy
jdk 动态代理
public class JdkProxyDemo { interface Foo { void foo(); } static final class Target implements Foo { public void foo() { System.out.println("target foo"); } } // jdk 只能针对接口代理 public static void main(String[] param) throws IOException { // 目标对象 Target target = new Target(); ClassLoader loader = JdkProxyDemo.class.getClassLoader(); // 用来加载在运行期间动态生成的字节码 Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> { System.out.println("before..."); // 目标.方法(参数) // 方法.invoke(目标, 参数); Object result = method.invoke(target, args); System.out.println("after...."); return result; // 让代理也返回目标方法执行的结果 }); System.out.println(proxy.getClass()); proxy.foo(); System.in.read(); } }
运行结果
proxy before... target foo proxy after...
注意:jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
模拟 jdk 动态代理
public class A12 { interface Foo { void foo(); int bar(); } static class Target implements Foo { public void foo() { System.out.println("target foo"); } public int bar() { System.out.println("target bar"); return 100; } } public static void main(String[] param) { // ⬇️1. 创建代理,这时传入 InvocationHandler Foo proxy = new $Proxy0(new InvocationHandler() { // ⬇️5. 进入 InvocationHandler public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ // ⬇️6. 功能增强 System.out.println("before..."); // ⬇️7. 反射调用目标方法 return method.invoke(new Target(), args); } }); // ⬇️2. 调用代理方法 proxy.foo(); proxy.bar(); } }
模拟代理实现
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; // ⬇️这就是 jdk 代理类的源码, 秘密都在里面 public class $Proxy0 extends Proxy implements A12.Foo { public $Proxy0(InvocationHandler h) { super(h); } // ⬇️3. 进入代理方法 public void foo() { try { // ⬇️4. 回调 InvocationHandler h.invoke(this, foo, new Object[0]); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public int bar() { try { Object result = h.invoke(this, bar, new Object[0]); return (int) result; } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } static Method foo; static Method bar; static { try { foo = A12.Foo.class.getMethod("foo"); bar = A12.Foo.class.getMethod("bar"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } }
代理一点都不难,无非就是利用了多态、反射的知识
- 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
- 通过接口回调将【增强逻辑】置于代理类之外
- 配合接口方法反射(是多态调用),就可以再联动调用目标方法
- 会用 arthas 的 jad 工具反编译代理类
限制⛔:
由于JDK动态代理是基于接口实现的,所以它只能代理接口中的方法,而不能代理类中的成员变量、静态方法以及final方法。
成员变量是对象的属性,与方法不同,成员变量并不是接口中定义的一部分,所以不能通过代理实现。静态方法属于类而不是对象,因此即使代理了对象,也无法代理类的静态方法。final方法在编译期就已经绑定到方法调用点,因此不能被代理。
需要注意的是,虽然JDK动态代理不能代理类中的成员变量、静态方法以及final方法,但是它可以代理接口中的默认方法,因为默认方法是接口中的一种特殊方法,可以在接口中定义和实现。
如果需要代理类中的成员变量、静态方法以及final方法,可以考虑使用其他类型的代理,例如CGLIB代理或者字节码操作库ASM。
方法反射优化
- 前 16 次反射性能较低
- 第 17 次调用会生成代理类,优化为非反射调用
cglib 代理
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
代码演示:
public class CglibProxyDemo { static class Target { public void foo() { System.out.println("target foo"); } } // 代理是子类型, 目标是父类型 public static void main(String[] param) { Target target = new Target(); Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> { System.out.println("before..."); // Object result = method.invoke(target, args); // 用方法反射调用目标 // methodProxy 它可以避免反射调用 // Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring) Object result = methodProxy.invokeSuper(p, args); // 内部没有用反射, 需要代理 System.out.println("after..."); return result; }); proxy.foo(); } }
注意:调用目标时有所改进,见下面代码片段
- method.invoke 是反射调用,必须调用到足够次数才会进行优化
- methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
- methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
- cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
- 限制⛔:根据上述分析 final 类无法被 cglib 增强
jdk 和 cglib 在 Spring 中的统一
Spring 中对切点、通知、切面的抽象如下
- 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
- 通知:典型接口为 MethodInterceptor 代表环绕通知
- 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut
相关术语
两个切面概念 aspect = 通知1(advice) + 切点1(pointcut) 通知2(advice) + 切点2(pointcut) 通知3(advice) + 切点3(pointcut) ... advisor = 更细粒度的切面,包含一个通知和切点
代理相关类图
- AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
- AopProxy 通过 getProxy 创建代理对象
- 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
- 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
public class ProxyTest { public static void main(String[] args) { // 1. 备好切点 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("execution(* foo())"); // 2. 备好通知 MethodInterceptor advice = invocation -> { System.out.println("before..."); Object result = invocation.proceed(); // 调用目标 System.out.println("after..."); return result; }; // 3. 备好切面 DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice); /* 4. 创建代理 a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现 b. proxyTargetClass = false, 目标没有实现接口, 用 cglib 实现 c. proxyTargetClass = true, 总是使用 cglib 实现 */ Target2 target = new Target2(); ProxyFactory factory = new ProxyFactory(); factory.setTarget(target); factory.addAdvisor(advisor); factory.setInterfaces(target.getClass().getInterfaces()); factory.setProxyTargetClass(false); Target2 proxy = (Target2) factory.getProxy(); System.out.println(proxy.getClass()); proxy.foo(); proxy.bar(); } interface I1 { void foo(); void bar(); } static class Target1 implements I1 { public void foo() { System.out.println("target1 foo"); } public void bar() { System.out.println("target1 bar"); } } static class Target2 { public void foo() { System.out.println("target2 foo"); } public void bar() { System.out.println("target2 bar"); } }
收获:
- 底层的切点实现
- 底层的通知实现
- 底层的切面实现
- ProxyFactory 用来创建代理
- 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
- 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
- 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy
注意
- 要区分本章节提到的 MethodInterceptor,它与之前 cglib 中用的的 MethodInterceptor 是不同的接口
切点匹配
收获
a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法
b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法的匹配
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException { // AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut(); // pt1.setExpression("execution(* bar())"); // System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class)); // System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class)); // // AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut(); // pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)"); // System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class)); // System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class)); StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() { @Override public boolean matches(Method method, Class<?> targetClass) { // 检查方法上是否加了 Transactional 注解 MergedAnnotations annotations = MergedAnnotations.from(method); if (annotations.isPresent(Transactional.class)) { return true; } // 查看类上是否加了 Transactional 注解 annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY); if (annotations.isPresent(Transactional.class)) { return true; } return false; } }; System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class)); System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class)); System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class)); System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class)); } static class T1 { @Transactional public void foo() { } public void bar() { } } @Transactional static class T2 { public void foo() { } } @Transactional interface I3 { void foo(); } static class T3 implements I3 { public void foo() { } } }