aop实现方式及基本使用

aop实现方式

  • aspectj 编译器增强,直接修改源码可以不借助Spring实现 也没有用代理对象 (ajc编译器)

    aop 的原理并非代理一种, 编译器也能玩出花样(直接修改源码)

  • 运行时需要在 VM options 里加入 -javaagent:D:/environment/apache-maven-3.6.3/repository/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
    把其中 D:/environment/apache-maven-3.6.3/repository 改为自己 maven 仓库起始地址

    aop 的原理并非代理一种, agent 也能, 只要字节码变了, 行为就变了(类加载阶段增强)

  • 代理 jdk or cglib(spring中)

jdk代理(基于接口和实现类,代理类与实现类平级)

public class JdkProxyDemo {interface Foo {void foo();}static final class Target implements Foo {public void foo() {System.out.println("target foo");}}// jdk 【只能针对接口代理】 代理类和被代理类是兄弟关系,平级的,被代理类 是 final也无关紧要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; // 让代理也返回目标方法执行的结果});// 使用arthas工具将动态生成字节码文件反编译为class类文件System.out.println(proxy.getClass());proxy.foo();System.in.read();}
}

cglib代理(基于子父类关系,父子关系)

public class CglibProxyDemo {static class Target {public void foo() {System.out.println("target foo");}}// 代理是子类型,目标是父类型 目标类加 final 就无法创建子类,直接报错 【cglib 基于子父类关系代理】// 增强目标不能是 final 增强方法也不能是 finalpublic 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();}
}

jdk代理原理【InvocationHandler】

接口实现类

    interface Foo {void foo();int bar();}static class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic int bar() {System.out.println("target bar");return 100;}}

asm技术生成字节码反编译生成类与手动实现代理类类似(可以使用arthas工具将字节码反编译生成class文件)【代理类】

运行程序,打开arthas工具

image-20230728232732035

看到运行程序,输入3,回车

输入如下命令,将字节码反编译为Java类文件

image-20230728232945215

/*** asm生成字节码,反编译生成的Java代理类,与手动实现类似*/
final class $Proxy0 extends Proxy implements JdkProxyDemo.Foo {private static final Method m0;private static final Method m1;private static final Method m2;private static final Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic final void foo() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}static {try {m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.lkl.spring.chapter11.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {return MethodHandles.lookup();}throw new IllegalAccessException(lookup.toString());}
}
/*** JDK动态代理实现相同接口,生成代理类(手动实现代理类)*/
public class $Proxy0 extends Proxy implements JDKTest.Foo {public $Proxy0(InvocationHandler h) {super(h);}@Overridepublic void foo() {try {// 不确定的方法使用接口抽象h.invoke(this, foo, new Object[0]);// 有异常直接抛出} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic 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 = JDKTest.Foo.class.getMethod("foo");bar = JDKTest.Foo.class.getMethod("bar");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}
}

调用生成代理类实现增强:

        // 生成代理对象Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before...");return method.invoke(new Target(), args);}});// java 动态代理 直接生成字节码 【asm技术】proxy.foo();System.out.println(proxy.bar());

全过程:

image-20230716172315945

cglib代理原理【MethodInterceptor】

目标类:

public class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long j) {System.out.println("save(long)");}
}

代理类:

public class Proxy extends Target {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static MethodProxy save0Proxy;static MethodProxy save1Proxy;static MethodProxy save2Proxy;/*cglib 一个代理类会生成两个代理对象jdk 大于16次,一个就会产生一个代理对象*/static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);/*五个参数:目标类型代理类型方法参数和返回值增强方案名原始方法名*/save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法public void saveSuper() {super.save();}public void saveSuper(int i) {super.save(i);}public void saveSuper(long j) {super.save(j);}// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long j) {try {methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}

和 jdk 动态代理原理查不多

  1. 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
  2. 调用目标时有所改进,见下面代码片段
    1. method.invoke 是反射调用,必须调用到足够次数才会进行优化
    2. methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
    3. methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
public class CglibTest {public static void main(String[] args) {Proxy proxy = new Proxy();Target target = new Target();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {return method.invoke(target, args); // 反射调用// FastClass(实际上还是代理) MethodProxy
//                return methodProxy.invoke(target, args); // 内部无反射,结合目标用
//                return methodProxy.invokeSuper(p, args); // 内部无反射,结合代理用}});proxy.save();proxy.save(1);proxy.save(2L);}
}

cglib 避免反射调用

  1. 当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类(父类都是FastClass
    • ProxyFastClass 配合代理对象一起使用, 避免反射
    • TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
  2. TargetFastClass 记录了 Target 中方法与编号的对应关系(Signature
    • save(long) 编号 2
    • save(int) 编号 1
    • save() 编号 0
    • 首先根据方法名和参数个数、类型, 用 switch 或 if 找到这些方法编号
    • 然后再根据编号去调用目标方法,,又用了一大堆 switch 或 if,但避免了反射
  3. ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法
    • saveSuper(long) 编号 5,不增强,仅是调用 super.save(long)
    • saveSuper(int) 编号 4,不增强, 仅是调用 super.save(int)
    • saveSuper() 编号 3,不增强, 仅是调用 super.save()
    • 查找方式与 TargetFastClass 类似
  4. 为什么有这么麻烦的一套东西呢?
    • 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
    • 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
/*** 需要代理对象*/
public class ProxyFastClass {static Signature s0 = new Signature("saveSuper", "()V");static Signature s1 = new Signature("saveSuper", "(I)V");static Signature s2 = new Signature("saveSuper", "(J)V");// 获取代理方法的编号/*ProxysaveSuper()              3saveSuper(int)           4saveSuper(long)          5signature 包括方法名字、参数返回值*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 3;} else if (s1.equals(signature)) {return 4;} else if (s2.equals(signature)) {return 5;}return -1;}// 根据方法编号, 正常调用目标对象方法public Object invoke(int index, Object proxy, Object[] args) {if (index == 3) {((Proxy) proxy).saveSuper();return null;} else if (index == 4) {((Proxy) proxy).saveSuper((int) args[0]);return null;} else if (index == 5) {((Proxy) proxy).saveSuper((long) args[0]);return null;} else {throw new RuntimeException("无此方法");}}public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper", "()V"));System.out.println(index);fastClass.invoke(index, new Proxy(), new Object[0]);}
}
/*** 需要目标对象*/
public class TargetFastClass {static Signature s0 = new Signature("save", "()V");static Signature s1 = new Signature("save", "(I)V");static Signature s2 = new Signature("save", "(J)V");// 获取目标方法的编号/*Targetsave()              0save(int)           1save(long)          2signature 包括方法名字、参数返回值*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;} else if (s1.equals(signature)) {return 1;} else if (s2.equals(signature)) {return 2;}return -1;}// 根据方法编号, 正常调用目标对象方法public Object invoke(int index, Object target, Object[] args) {if (index == 0) {((Target) target).save();return null;} else if (index == 1) {((Target) target).save((int) args[0]);return null;} else if (index == 2) {((Target) target).save((long) args[0]);return null;} else {throw new RuntimeException("无此方法");}}public static void main(String[] args) {TargetFastClass fastClass = new TargetFastClass();int index = fastClass.getIndex(new Signature("save", "(I)V"));System.out.println(index);fastClass.invoke(index, new Target(), new Object[]{100});}
}

配合代理类中 MethodProxy实现不反射调用方法

jdk 和 cglib 在 Spring 中的统一

Spring 中对切点、通知、切面的抽象如下

  • 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
  • 通知:接口Advice,典型接口为 MethodInterceptor 代表环绕通知
  • 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut
«interface»
Advice
«interface»
MethodInterceptor
«interface»
Advisor
«interface»
PointcutAdvisor
«interface»
Pointcut
AspectJExpressionPointcut

代理相关类图

  • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
  • AopProxy 通过 getProxy 创建代理对象
  • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
  • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
使用
创建
创建
«interface»
Advised
ProxyFactory
proxyTargetClass : boolean
Target
Advisor
«interface»
AopProxyFactory
«interface»
AopProxy
+getProxy() : Object
基于CGLIB的Proxy
ObjenesisCglibAopProxy
advised : ProxyFactory
JdkDynamicAopProxy
advised : ProxyFactory
基于JDK的Proxy

ProxyFactory 用来创建代理

  • 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
  • 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
    • 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy
public class A15 {public static void main(String[] args) {/*两个切面概念aspect =通知1(advice) +  切点1(pointcut)通知2(advice) +  切点2(pointcut)通知3(advice) +  切点3(pointcut)...advisor = 更细粒度的切面,包含一个通知和切点*/// 1. 备好切点 Pointcut实现AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");// 2. 备好通知 Advice实现MethodInterceptor advice = invocation -> {System.out.println("before...");Object result = invocation.proceed(); // 调用目标System.out.println("after...");return result;};// 3. 备好切面 Advisor实现DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);/*4. 创建代理 jdk or cglib ?a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现b. proxyTargetClass = false,  目标没有实现接口, 用 cglib 实现c. proxyTargetClass = true, 总是使用 cglib 实现*/Target1 target = new Target1();ProxyFactory factory = new ProxyFactory();factory.setTarget(target);factory.addAdvisor(advisor);factory.setInterfaces(target.getClass().getInterfaces());factory.setProxyTargetClass(false);I1 proxy = (I1) factory.getProxy();System.out.println(proxy.getClass());proxy.foo();proxy.bar();/*学到了什么a. Spring 的代理选择规则b. 底层的切点实现c. 底层的通知实现d. ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现- JdkDynamicAopProxy- ObjenesisCglibAopProxy*/}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");}}
}

切点匹配(matches)

  1. 常见 aspectj 切点用法,切点为方法或者注解

  2. aspectj 切点的局限性,实际的 @Transactional 切点实现

    @Transactional注解
    放在方法上,表示该方法具有事务能力
    放在类上,类中所有方法都具有事务能力
    放在接口上,接口实现类所有方法都具有事务能力

public class A16 {public static void main(String[] args) throws NoSuchMethodException {AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();// matches 判断是否是我们表达式想要的内容 Expression表达式pt1.setExpression("execution(* bar())"); // 方法System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class)); // falseSystem.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class)); // trueAspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();// 注解pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class)); // trueSystem.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class)); // false/*@Transactional注解放在方法上,表示该方法具有事务能力放在类上,类中所有方法都具有事务能力放在接口上,接口实现类所有方法都具有事务能力*/StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {@Overridepublic 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)); // trueSystem.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class)); // falseSystem.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class)); // trueSystem.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class)); // true/*学到了什么a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法(matches)的匹配*/}static class T1 {@Transactionalpublic void foo() {}public void bar() {}}@Transactionalstatic class T2 {public void foo() {}}@Transactionalinterface I3 {void foo();}static class T3 implements I3 {public void foo() {}}
}

从 @Aspect 到 Advisor(高级切面转换为低级切面)

AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator BeanPostProcessor 解析处理切面类,由该处理器统一转换为低级切面

该处理器类可以作用在依赖注入之前以及初始化之后,两个选其一

创建(实例化) -> (/) 依赖注入 -> 初始化 (/)

findEligibleAdvisors

findEligibleAdvisors 找到有【资格】的 Advisors (切点包含对应类中方法)

  • 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如下advisor3
  • 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得
public class A17 {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("aspect1", Aspect1.class);context.registerBean("config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);// BeanPostProcessor 解析处理切面类// 创建 -> (*) 依赖注入 -> 初始化 (*)context.refresh();
//        for (String name : context.getBeanDefinitionNames()) {
//            System.out.println(name);
//        }// 保证同包,能调用对应 protected 方法AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);List<Advisor> advisors = creator.findEligibleAdvisors(Target2.class, "target2");for (Advisor advisor : advisors) {System.out.println(advisor);}}static class Target1 {public void foo() {System.out.println("target1 foo");}}static class Target2 {public void bar() {System.out.println("target2 bar");}}@Aspect // 高级切面类@Order(1)static class Aspect1 {@Before("execution(* foo())")public void before1() {System.out.println("aspect1 before...");}@After("execution(* foo())")public void before2() {System.out.println("aspect1 after...");}}@Configurationstatic class Config {@Bean // 低级切面public Advisor advisor3(MethodInterceptor advice3) {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);advisor.setOrder(2);return advisor;}@Beanpublic MethodInterceptor advice3() {return invocation -> {System.out.println("advice3 before...");Object result = invocation.proceed();System.out.println("advice3 after...");return result;};}}}

保证类同包,为了调用protected权限方法

image-20230726120034934

上述输出是四个Advisor,一个默认,一个Aspect1类(高级切面)解析为两个,还有一个advisor3(低级切面)

image-20230726135116734

wrapIfNecessary

  • 内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理
  • 调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
        Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");// 有对应Advisor,生成代理对象System.out.println(o1.getClass());Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");// 普通对象System.out.println(o2.getClass());// 执行方法会被增强((Target1) o1).foo();

image-20230726141112257

代理创建时机

  • 代理的创建时机

    • 初始化之后 (无循环依赖时)
    • 实例创建后,依赖注入前 (有循环依赖时),并暂存于二级缓存

    (假设Bean1与Bean2存在循环依赖,Bean1初始化过程中会先将Bean1实例化但未初始化的对象放入二级缓存,将Bean2对象创建完成,再回来继续完善Bean1对象,此时Bean2依赖Bean1一定是Bean1代理对象,因此代理创建时机提前至实例化后依赖注入前)

  • 依赖注入与初始化不应该被增强, 仍应被施加于原始对象

模拟将高级切面转换成低级切面,统一转成环绕通知形成通知链

其实无论 ProxyFactory 基于哪种方式创建代理, 最后干活(调用advice)的是一个 MethodInvocation 对象
a. 因为 advisor 有多个, 且一个套一个调用, 因此需要一个**【调用链】对象**, 即 MethodInvocation
b. MethodInvocation 要知道 advice 有哪些, 还要知道目标, 调用次序如下

​ 将 MethodInvocation 放入当前线程
​ |-> before1 ----------------------------------- 从当前线程获取 MethodInvocation
​ | |
​ | |-> before2 -------------------- | 从当前线程获取 MethodInvocation
​ | | | |
​ | | |-> target ------ 目标 advice2 advice1
​ | | | |
​ | |-> after2 --------------------- |
​ | |
​ |-> after1 ------------------------------------
​ c. 从上图看出, 环绕通知才适合作为 advice, 因此其他 before、afterReturning 都会被转换成环绕通知
​ d. 统一转换为环绕通知, 体现的是设计模式中的【适配器模式】
​ - 对外是为了方便使用要区分 before、afterReturning
​ - 对内统一都是环绕通知, 统一用 MethodInterceptor 表示

执行之前获取所有执行时需要的 advice (静态)
a. 即统一转换为 MethodInterceptor 环绕通知, 这体现在方法名中的 Interceptors 上
b. 适配如下
- MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
- AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor

适配器类代码很简单,实际上就做了一个类型转换:

image-20230726142739409

public class A18 {static class Aspect {@Before("execution(* foo())")public void before1() {System.out.println("before1");}@Before("execution(* foo())")public void before2() {System.out.println("before2");}public void after() {System.out.println("after");}@AfterReturning("execution(* foo())")public void afterReturning() {System.out.println("afterReturning");}@AfterThrowing("execution(* foo())")public void afterThrowing(Exception e) {System.out.println("afterThrowing " + e.getMessage());}@Around("execution(* foo())")public Object around(ProceedingJoinPoint pjp) throws Throwable {try {System.out.println("around...before");return pjp.proceed();} finally {System.out.println("around...after");}}}static class Target {public void foo() {System.out.println("target foo");}}public static void main(String[] args) throws Throwable {AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());// 1. 高级切面转低级切面类List<Advisor> list = new ArrayList<>();for (Method method : Aspect.class.getDeclaredMethods()) {if (method.isAnnotationPresent(Before.class)) {// 解析切点String expression = method.getAnnotation(Before.class).value();AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);// 通知类 不同类型通知对应不同切面类 (前置通知切面类)AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);// 封装成切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);list.add(advisor);} else if (method.isAnnotationPresent(AfterReturning.class)) {// 解析切点String expression = method.getAnnotation(AfterReturning.class).value();AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);// 通知类AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);// 切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);list.add(advisor);} else if (method.isAnnotationPresent(Around.class)) {// 解析切点String expression = method.getAnnotation(Around.class).value();AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);// 通知类AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);// 切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);list.add(advisor);}}// 低级切面列表for (Advisor advisor : list) {System.out.println(advisor);}/*@Before 前置通知会被转换为下面原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息a. 通知代码从哪儿来b. 切点是什么c. 通知对象如何创建, 本例共用同一个 Aspect 对象类似的通知还有1. AspectJAroundAdvice (环绕通知)2. AspectJAfterReturningAdvice3. AspectJAfterThrowingAdvice (环绕通知)4. AspectJAfterAdvice (环绕通知)*/// 2. 通知统一转换为环绕通知 MethodInterceptorTarget target = new Target();ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(target);proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程proxyFactory.addAdvisors(list);System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");// 将不是环绕通知的通知,统一转成环绕通知 形成通知链List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);for (Object o : methodInterceptorList) {System.out.println(o);}System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");// 3. 创建并执行调用链 (环绕通知s + 目标)MethodInvocation methodInvocation = new ReflectiveMethodInvocation(null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList);// 执行,方法得到增强methodInvocation.proceed();/*学到了什么a. 无参数绑定的通知如何被调用b. MethodInvocation 编程技巧: 拦截器、过滤器等等实现都与此类似c. 适配器模式在 Spring 中的体现*/}
}

模拟调用链过程

模拟调用链过程,实际上是一个递归过程【责任链模式】

  1. proceed() 方法调用链中下一个环绕通知
  2. 每个环绕通知内部继续调用 proceed()
  3. 调用到没有更多通知了, 就调用目标方法,结束递归
public class A18_1 {static class Target {// 待增强方法public void foo() {System.out.println("Target.foo()");}}// 两个切面类	static class Advice1 implements MethodInterceptor {public Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("Advice1.before()");Object result = invocation.proceed();// 调用下一个通知或目标System.out.println("Advice1.after()");return result;}}static class Advice2 implements MethodInterceptor {public Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("Advice2.before()");Object result = invocation.proceed();// 调用下一个通知或目标System.out.println("Advice2.after()");return result;}}// 调用链对象static class MyInvocation implements MethodInvocation {// 目标对象,反射调用目标方法使用private Object target;  private Method method;private Object[] args;// 环绕通知类列表List<MethodInterceptor> methodInterceptorList; private int count = 1; // 调用次数,实际上就是切面类方法调用次数public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {this.target = target;this.method = method;this.args = args;this.methodInterceptorList = methodInterceptorList;}@Overridepublic Method getMethod() {return method;}@Overridepublic Object[] getArguments() {return args;}/*** 调用每一个环绕通知,调用目标 关键!!! 递归调用*/@Overridepublic Object proceed() throws Throwable {if (count > methodInterceptorList.size()) {// 所有切面类调用完毕,调用目标方法,返回并结束递归return method.invoke(target, args);}// 逐一调用通知, count + 1MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);// 执行切面类方法,形成递归调用return methodInterceptor.invoke(this);}@Overridepublic Object getThis() {return target;}@Overridepublic AccessibleObject getStaticPart() {return method;}}public static void main(String[] args) throws Throwable {Target target = new Target();List<MethodInterceptor> list = new ArrayList<>() {{add(new Advice1());add(new Advice2());}};MyInvocation invocation = new MyInvocation(target, Target.class.getMethod("foo"), new Object[0], list);invocation.proceed();}
}

达成效果

image-20230726151358815

静态通知调用

无参数通知切点

代理对象调用流程如下(以 JDK 动态代理实现为例)

  • 从 ProxyFactory 获得 Target 和环绕通知链,根据他俩创建 MethodInvocation,简称 mi
  • 首次执行 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知1,执行前增强,再次调用 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法
  • 目标方法执行结束,将结果返回给环绕通知2,执行环绕通知2 的后增强
  • 环绕通知2继续将结果返回给环绕通知1,执行环绕通知1 的后增强
  • 环绕通知1返回最终的结果

(本质是递归调用)

Proxy InvocationHandler MethodInvocation ProxyFactory MethodInterceptor1 MethodInterceptor2 Target invoke() 获得 Target 获得 MethodInterceptor 链 创建 mi mi.proceed() invoke(mi) 前增强 mi.proceed() invoke(mi) 前增强 mi.proceed() mi.invokeJoinPoint() 结果 后增强 结果 后增强 结果 Proxy InvocationHandler MethodInvocation ProxyFactory MethodInterceptor1 MethodInterceptor2 Target

代理方法执行时会做如下工作

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
    • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
    • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
    • 这体现的是适配器设计模式
  2. 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可
  3. 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用,体现的是责任链模式

动态通知调用

    @Aspectstatic class MyAspect {@Before("execution(* foo(..))") // 静态通知调用,不带参数绑定,执行时不需要切点public void before1() {System.out.println("before1");}@Before("execution(* foo(..)) && args(x)") // 动态通知调用,需要参数绑定,执行时还需要切点对象public void before2(int x) {System.out.printf("before2(%d)%n", x);}}

带参数通知切点

  • 所谓动态通知,体现在上面方法的 DynamicInterceptionAdvice 部分,这些通知调用时因为要为通知方法绑定参数,还需再次利用切点表达式

带参数切点会被封装成如下对象:

class InterceptorAndDynamicMethodMatcher {// 环绕通知final MethodInterceptor interceptor;// 切点final MethodMatcher methodMatcher;public InterceptorAndDynamicMethodMatcher(MethodInterceptor interceptor, MethodMatcher methodMatcher) {this.interceptor = interceptor;this.methodMatcher = methodMatcher;}}
  • 动态通知调用复杂程度高,性能较低

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/12130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Matlab的SimuLink对FS32K144编程--内部数据存储Flash

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 前言 Flah擦写是由寿命的&#xff0c;应当减免无效的擦写&#xff0c;如数据值不变不进行擦写 1、新建工程完成后&#xff0c;拖出Flash的存储控制初始化…

ROS 2 — 托管(生命周期)节点简介

一、说明 这篇文章是关于理解ROS 2中托管&#xff08;生命周期&#xff09;节点的概念。我们描述了概念性的想法以及我们为什么需要它。所以让我们开始吧&#xff01; 二、托管式节点 — 什么和为什么&#xff1f; 为了理解托管式节点&#xff0c;让我们从一个简单的问题陈述开…

串口通讯接口类型:TTL、RS232和RS485(电平标准)

串口通讯接口类型&#xff1a;TTL、RS232和RS485 在串口通信中&#xff0c;常用的接口类型包括TTL、RS-232和RS-485&#xff0c;TTL、RS-232、RS422、RS-485是指的电平标准(电信号)。 通信协议规定了数据传输的规则和格式&#xff0c;包括数据的起始位、停止位、数据位数、校…

内网穿透远程查看内网监控摄像头

内网穿透远程查看内网监控摄像头 在现代社会中&#xff0c;大家总是奔波于家和公司之间。大部分时间用于工作中&#xff0c;也就很难及时知晓家中的动态情况&#xff0c;对于家中有老人、小孩或宠物的&#xff08;甚至对居住环境安全不放心的&#xff09;&#xff0c;这已然是…

Retrospectives on the Embodied AI Workshop(嵌入式人工智能研讨会回顾) 论文阅读

论文信息 题目&#xff1a;Retrospectives on the Embodied AI Workshop 作者&#xff1a;Matt Deitke, Dhruv Batra, Yonatan Bisk 来源&#xff1a;arXiv 论文地址&#xff1a;https://arxiv.org/pdf/2210.06849 Abstract 我们的分析重点关注 CVPR Embodied AI Workshop 上…

JiaYu说:如何做好IT类的技术面试?

IT类的技术面试 面试IT公司的小技巧IT技术面试常见的问题嵌入式技术面试嵌入式技术面试常见的问题嵌入式软件/硬件面试题 JiaYu归属嵌入式行业&#xff0c;所以这里只是以普通程序员的角度去分析技术面试的技巧 当然&#xff0c;也对嵌入式技术面试做了小总结&#xff0c;友友们…

vite / nuxt3 项目使用define配置/自定义,可以使用process.env.xxx获取的环境变量

每日鸡汤&#xff1a;每个你想要学习的瞬间&#xff0c;都是未来的你向自己求救 首先可以看一下我的这篇文章了解一下关于 process.env 的环境变量。 对于vite项目&#xff0c;在我们初始化项目之后&#xff0c;在浏览器中打印 process.env&#xff0c;只有 NODE_ENV这个变量&…

【组内工作】木马回联

文章目录 C2服务器安装和运行方法CrossC2运行方法sliver运行方法empire安装方法DeimosC2安装教程TrevorC2安装教程&#xff1a; C2服务器的流量特征CrossC21. 心跳包2. 命令3. ja3/ja3s Sliver1. http2. https empirehttphttps DeimosC2https TrevorC2 C2服务器安装和运行方法 …

iperf3 编译安装及网讯WX1860千兆网口测试

iperf3 编译安装及网讯1860千兆网口测试 编译安装 安装包下载地址:https://github.com/esnet/iperf/archive/refs/tags/3.8.tar.gz 将安装包iperf-3.8.tar.gz拷贝测试系统盘桌面,使用如下命令进行编译安装: tar zxvf iperf-3.8.tar.gz cd iperf-3.8 ./configure make s…

HDFS基本操作命令

这里写目录标题 HDFS Shell CLI客户端说明常用命令hadoop fs -mkdir [-p] <path>hadoop fs -ls [-h] [-R] [<path>...]上传文件到指定目录下方法一:hadoop fs -put [-f] [-p] <localsrc>.....<dst>方法二&#xff1a;hadoop fs -moveFromLocal <loc…

Vue源码学习 - 异步更新队列 和 nextTick原理

目录 前言一、Vue异步更新队列二、nextTick 用法三、原理分析四、nextTick 源码解析1&#xff09;环境判断2&#xff09;nextTick() 五、补充 前言 在我们使用Vue的过程中&#xff0c;基本大部分的 watcher 更新都需要经过 异步更新 的处理。而 nextTick 则是异步更新的核心。…

MacOS本地安装Hadoop3

金翅大鹏盖世英&#xff0c;展翅金鹏盖世雄。 穿云燕子锡今鸽&#xff0c;踏雪无痕花云平。 ---------------- 本文密钥&#xff1a;338 ----------------- 本文描述了在macbook pro的macos上安装hadoop3的过程&#xff0c;也可以作为在任何类linux平台上安装hadoop3借鉴。 …

4、Linux驱动开发:设备-设备号设备号注册

目录 &#x1f345;点击这里查看所有博文 随着自己工作的进行&#xff0c;接触到的技术栈也越来越多。给我一个很直观的感受就是&#xff0c;某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了&#xff0c;只有经常会用到的东西才有可能真正记…

Verilog语法学习——LV2_异步复位的串联T触发器

LV2_异步复位的串联T触发器 题目来源于牛客网 [牛客网在线编程_Verilog篇_Verilog快速入门 (nowcoder.com)](https://www.nowcoder.com/exam/oj?page1&tabVerilog篇&topicId301) 题目 题目描述&#xff1a; 用verilog实现两个串联的异步复位的T触发器的逻辑&#x…

【LeetCode】141.环形链表

题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#…

opencv-22 图像几何变换01-缩放-cv2.resize()(图像增强,图像变形,图像拼接)

什么是几何变换&#xff1f; 几何变换是计算机图形学中的一种图像处理技术&#xff0c;用于对图像进行空间上的变换&#xff0c;而不改变图像的内容。这些变换可以通过对图像中的像素位置进行调整来实现。 常见的几何变换包括&#xff1a; 平移&#xff08;Translation&#x…

STM32MP157驱动开发——按键驱动(tasklet)

文章目录 “tasklet”机制&#xff1a;内核函数定义 tasklet使能/ 禁止 tasklet调度 tasklet删除 tasklet tasklet软中断方式的按键驱动程序(stm32mp157)tasklet使用方法&#xff1a;button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “tasklet”机制&#xff1a; …

【Ansible】Ansible自动化运维工具之playbook剧本

playbook 一、playbook 的概述1. playbook 的概念2. playbook 的构成 二、playbook 的应用1. 安装 httpd 并启动2. 定义、引用变量3. 指定远程主机 sudo 切换用户4. when条件判断5. 迭代6. Templates 模块6.1 添加模板文件6.2 修改主机清单文件6.3 编写 playbook 7. tags 模块 …

vue权限按钮的实现

鉴权函数 由于下面几种方式都需要用到鉴权函数&#xff0c;所以将其放置在组件外面&#xff0c;供组件或其他文件调用。 // src/utils/hasPermission.jsimport { usePermissionStore } from /stores import array from lodash/array export const hasPermission (value, def…

EXCEL,如何比较2个表里的数据差异(使用数据透视表)

目录 1 问题: 需要比较如下2个表的内容差异 1.1 原始数据喝问题 1.2 提前总结 2 使用EXCEL公式方法 2.1 新增辅助列&#xff1a; 辅助index 2.2 具体公式 配合条件格式 使用 3 数据透视表方法 3.1 新增辅助列&#xff1a; 辅助index 3.2 需要先打开 数据透视表向导 …