aop实现方式及基本使用

news2024/11/16 10:30:12

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 增强方法也不能是 final
    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();

    }
}

jdk代理原理【InvocationHandler】

接口实现类

    interface Foo {
        void foo();

        int bar();
    }

    static class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }

        @Override
        public 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);
        }
    }

    @Override
    public 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);
    }

    @Override
    public void foo() {
        try {
            // 不确定的方法使用接口抽象
            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 = 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() {
            @Override
            public 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);
    }
    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public 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() {
            @Override
            public 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");

    // 获取代理方法的编号
    /*
        Proxy
            saveSuper()              3
            saveSuper(int)           4
            saveSuper(long)          5
        signature 包括方法名字、参数返回值
     */
    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");

    // 获取目标方法的编号
    /*
        Target
            save()              0
            save(int)           1
            save(long)          2
        signature 包括方法名字、参数返回值
     */
    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)); // false
        System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class)); // true

        AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
        // 注解
        pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class)); // true
        System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class)); // false

        /*
            @Transactional注解
                 放在方法上,表示该方法具有事务能力
                 放在类上,类中所有方法都具有事务能力
                 放在接口上,接口实现类所有方法都具有事务能力
         */
        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)); // true
        System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class)); // false
        System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class)); // true
        System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class)); // true

        /*
            学到了什么
                a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法
                b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法(matches)的匹配
         */
    }


    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() {
        }
    }
}

从 @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...");
        }
    }

    @Configuration
    static 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;
        }

        @Bean
        public 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. AspectJAfterReturningAdvice
                3. AspectJAfterThrowingAdvice (环绕通知)
                4. AspectJAfterAdvice (环绕通知)
         */

        // 2. 通知统一转换为环绕通知 MethodInterceptor
        Target 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;
        }

        @Override
        public Method getMethod() {
            return method;
        }

        @Override
        public Object[] getArguments() {
            return args;
        }

        /**
         * 调用每一个环绕通知,调用目标 关键!!! 递归调用
         */
        @Override
        public Object proceed() throws Throwable {
            if (count > methodInterceptorList.size()) {
                // 所有切面类调用完毕,调用目标方法,返回并结束递归
                return method.invoke(target, args);
            }
            // 逐一调用通知, count + 1
            MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
            // 执行切面类方法,形成递归调用
            return methodInterceptor.invoke(this);
        }

        @Override
        public Object getThis() {
            return target;
        }

        @Override
        public 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 对象,通过它完成整个调用,体现的是责任链模式

动态通知调用

    @Aspect
    static 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.coloradmin.cn/o/806105.html

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

相关文章

阿里云服务器CPU大全_处理器主频性能说明

阿里云服务器CPU型号是什么&#xff1f;处理器主频多少&#xff1f;云服务器ECS和轻量应用服务器CPU处理器性能如何&#xff1f;阿里云服务器网分享阿里云服务器CPU型号大全、处理器主频性能型号汇总&#xff1a; 目录 阿里云服务器CPU处理器大全 通用型云服务器CPU 计算型…

【动态规划刷题 2】使⽤最⼩花费爬楼梯 解码⽅法

使⽤最⼩花费爬楼梯 746 . 使用最小花费爬楼梯 链接: 746 . 使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 …

《向量数据库指南》:向量数据库Pinecone如何集成hugging-face-endpoints

目录 端点 创建嵌入 Vector DB 创建和索引嵌入向量 拥抱面推理端点允许访问简单的模型推理。结合Pinecone,我们可以轻松生成和索引高质量的向量嵌入。 让我们通过初始化生成向量嵌入的推理端点来开始吧。 端点 我们首先前往拥抱面推理端点主页,如果需要,注册一个账…

某厂生产三种产品 I, II, III。 每种产品要经过 A、 B两道工序加工。 设该厂有两种规格的设备能完成 A 工序, 它们以A1、 A2表示; 有三种规格的设备能完成 B 工序, 它们以B1、

数学建模算法与应用习题1-3 通俗解析 一.题干二.解答2.1 先读懂题干2.2 解体思路 一.题干 某厂生产三种产品 I&#xff0c; II&#xff0c; III。 每种产品要经过 A、 B两道工序加工。 设该厂有两种规格的设备能完成 A 工序&#xff0c; 它们以A1、 A2表示&#xff1b; 有三种…

TreeMap的底层实现

0. 你需要知道的TreeMap的内置属性 0.1 节点属性 K key; // 键 V value; // 值 Entry<K,V> left; // 左子节点 Entry<K,V> right; // 右子节点 Entry<K,V> parent; // 父节点 boolean color; // 节点的颜色0.2 成员变量 //比较器对象private f…

Spring Boot配置加密实践

Spring Boot配置加密实践 使用Java技术栈的时候&#xff0c;Spring Boot几乎已经成为了标配。Spring Boot帮助我们简化了各种技术的整合&#xff0c;我们只需要在application.yml配置文件中增加一点点的配置即可。 虽然Spring Boot简化了我们的工作&#xff0c;但是也隐藏了底…

draw up a plan

爱情是美好的&#xff0c;却不是唯一的。爱情只是属于个人化的感情。 推荐一篇关于爱情的美文&#xff1a; 在一个小镇上&#xff0c;有一家以制作精美巧克力而闻名的手工巧克力店&#xff0c;名叫“甜蜜之爱”。这家巧克力店是由一位名叫艾玛的年轻女性经营的&#xff0c;她对…

【Git】版本回退与撤销修改案例

目录 一、版本回退 二、撤销修改案例 案例1&#xff1a;仅在工作区进行了修改还未进行add操作 案例2&#xff1a;仅进行了add 操作还未进行commit操作 案例3&#xff1a;进行了add与commit操作无其他操作 三、版本库中删除文件 一、版本回退 在进行版本回退之前我们需要…

类加载机制与类加载器

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ Java 源码是如何形成类文件的&#xff0c;类文件又是如何加载到虚拟机的&#xff0c;类加载有哪些机制和原则呢&#xff1f;本文将为大家一一介绍。 1 Java 源码形成类文件…

音乐编曲软件FL Studio21中文免费版新功能及下载

FL Studio21是一款数字音频工作站软件&#xff0c;用于音乐制作和混音。它具有广泛的音频编辑和处理功能&#xff0c;可以帮助你创建出专业的音乐和音频内容。它还支持多个音频和效果轨道&#xff0c;可以帮助你创建更加复杂和高质量的音频内容。FL Studio是一款著名的DAW音频制…

1300*C. A Cookie for You

Example input 6 2 2 1 2 0 100 0 1 12 13 25 1 27 83 14 25 0 0 1 0 1000000000000000000 1000000000000000000 1000000000000000000 1000000000000000000 output Copy Yes No No Yes No Yes 解析&#xff1a; 因为第二种人只能吃少的那种蛋糕&#xff0c;所以优先满足他…

Flutter 开发者工具 Android Studio 开发Flutter应用

Flutter 开发者工具 在 Android Studio 开发Flutter应用 &#x1f525; Android Studio 版本更新 &#x1f525; Android Studio Check for Update Connection failed ​ 解决方案 如果是运行的是32位的android studio需要在andriod studio的启动目录下找到studio.exe.vmoptio…

spring boot 2 配置上传文件大小限制

一、起因&#xff1a;系统页面上传一个文件超过日志提示的文件最大100M的限制&#xff0c;需要更改配置文件 二、经过&#xff1a; 1、在本地代码中找到配置文件&#xff0c;修改相应数值后交给运维更新生产环境配置&#xff0c;但是运维说生产环境没有这行配置&#xff0c;遂…

Java那些“锁”事 - 死锁及排查

死锁是两个或者两个以上的线程在执行过程中&#xff0c;因争夺资源而造成的一种互斥等待现象&#xff0c;若没有外界干涉那么它们将无法推进下去。如果系统资源不足&#xff0c;进程的资源请求都得到满足&#xff0c;死锁出现的可能性就很低&#xff0c;否则就会因为争夺有限的…

kali安装Docker的方法+一次错误体验(第一行错误)

一.kali安装docker #添加docker的gpg密钥&#xff0c;签名用的 curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian/gpg | sudo apt-key add -#添加docker的apt源 echo deb https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian/ buster sta…

使用Jetpack Compose和Motion Layout创建交互式UI

使用Jetpack Compose和Motion Layout创建交互式UI 通过阅读本博客&#xff0c;您将学会使用Motion Layout实现这种精致的动画效果&#xff1a; 让我们从简单的介绍开始。 介绍 作为Android开发者&#xff0c;您可能会遇到需要布局动画的情况&#xff0c;有时甚至需要变形样…

Linux安装kafka3.5.1

要在Ubuntu上安装Apache Kafka&#xff0c;请按照以下步骤操作&#xff1a; 1、安装Java运行时环境(Ubuntu)&#xff1a; 如果已经安装jdk不用执行 sudo apt update sudo apt install default-jre2、下载Kafka&#xff1a; wget https://downloads.apache.org/kafka/3.5.1/…

Docker容器基本操作之启动-停止-重启

一、安装启动RabbitMQ容器 此处以rabbitmq容器为例 前提&#xff1a;需要安装配置好docker(设置镜像源、配置阿里云加速)、开启docker&#xff0c;停止(stop)或者禁用(disable)手动解压缩安装的rabbitmq,以防与docker中安装的rabbitmq冲突。 //查看docker状态 systemctl stat…

android Glide加载gif动图和本地视频,Java

droid Glide加载gif动图和本地视频&#xff0c;Java //从手机存储本地加载视频 String filePath "/storage/emulated/0/Pictures/my_video.mp4"; Glide .with( context ).load( Uri.fromFile( new File( filePath ) ) ).into( imageView );//加载gif Glide .with(…

用群晖NAS搭建个人音乐库

安装教程 勾选启动NTP服务 1. 群晖安装Audio Station 2. filestation会生成一个music文件夹&#xff0c;把下载好的音乐丢进music即可&#xff08;音乐平台听不到的歌也顺带通过下载解决了&#xff09;这时候你就可以在audio station直接听歌了不过咱们大部分时候听歌是在手机…