zoukankan      html  css  js  c++  java
  • Spring AOP 代理创建方式

    这里是指 Spring 应用层的方式,不是指底层实现的方式。

    底层实现方式熟悉的有两种:JDK 动态代理和 CGLIB 代理:https://www.cnblogs.com/jhxxb/p/10520345.html

    Spring 应用层提供了多种代理创建方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory

    pom 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>

    ProxyFactoryBean

    // 业务
    public interface MathCalculator {
        int div(int i, int j);
    }
    @Service
    public class MathCalculatorImpl implements MathCalculator {
        @Override
        public int div(int i, int j) {
            System.out.println("MathCalculator...div...方法执行");
            return i / j;
        }
    }
    
    // 定义一个前置通知
    @Component("logMethodBeforeAdvice")
    public class LogMethodBeforeAdvice implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("this is LogMethodBeforeAdvice");
        }
    }
    
    // 注册一个代理 Bean
    public class AopConfig {
        @Bean
        public ProxyFactoryBean proxyFactoryBean(MathCalculator mathCalculator) {
            ProxyFactoryBean factoryBean = new ProxyFactoryBean();
    
            // 代理的目标对象  效果同setTargetSource(@Nullable TargetSource targetSource)
            // 此处需要注意的是,这里如果直接new,那么该类就不能使用@Autowired之类的注入  因此建议此处还是从容器中去拿
            // 因此可以写在入参上(这也是标准的写法~~)
            // factoryBean.setTarget(new HelloServiceImpl());
            factoryBean.setTarget(mathCalculator);
    
            // setInterfaces和setProxyInterfaces的效果是相同的。设置需要被代理的接口,
            // 若没有实现接口,那就会采用cglib去代理
            // 需要说明的一点是:这里不设置也能正常被代理(若你没指定,Spring 内部会去帮你找到所有的接口,然后全部代理上)设置的好处是只代理指定的接口
            factoryBean.setInterfaces(MathCalculator.class);
            // factoryBean.setProxyInterfaces(new Class[]{HelloService.class});
    
            // 需要植入进目标对象的bean列表 此处需要注意:这些bean必须实现类 org.aopalliance.intercept.MethodInterceptor或 org.springframework.aop.Advisor的bean ,配置中的顺序对应调用的顺序
            factoryBean.setInterceptorNames("logMethodBeforeAdvice");
    
            // 若设置为 true,强制使用 cglib,默认是 false 的
            // factoryBean.setProxyTargetClass(true);
    
            return factoryBean;
        }
    }
    
    public class AopTest {
        @Test
        public void aopTest() {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
            System.out.println("SpringVersion: " + SpringVersion.getVersion());
            System.out.println("================================================");
    
            // expected single matching bean but found 2: mathCalculatorImpl,proxyFactoryBean
            // 如果通过类型获取,会找到两个 Bean:一个我们自己的实现类、一个 ProxyFactoryBean 所生产的代理类,而此处我们显然是希望要生成的代理类的,因此我们只能通过名称来(或者加上 @Primary)
            // MathCalculator bean = applicationContext.getBean(MathCalculator.class);
            MathCalculator bean = (MathCalculator) applicationContext.getBean("proxyFactoryBean");
            bean.div(1, 1);
            System.out.println("================================================");
    
            System.out.println(bean);
            System.out.println(bean.getClass()); // class com.sun.proxy.$Proxy28 用的 JDK 动态代理
            // 顺便说一句:这样也是没错得。因为 Spring AOP 代理出来的每个代理对象,都默认实现了这个接口(它是个标记接口)
            // 它这个也就类似于所有的 JDK 代理出来的,都是 Proxy 的子类是一样的思想
            SpringProxy springProxy = (SpringProxy) bean;
    
            System.out.println("================================================");
            mathCalculator.div(1, 0);
    
            applicationContext.close();
        }
    }

    虽然很多时候都是结合 IOC 容器一起使用,但是它并不依赖 IOC:

    public class PointcutTest {
        @Test
        public void expressionPointcutTest() {
            String pointcutExpression = "execution(int aop.PointcutTest.Person.run())"; // 会拦截 Person.run() 方法
            // String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。  比如:run() 会拦截,但是 run(int i) 不会被拦截
            // AspectJExpressionPointcut 支持的表达式一共有 11 种(也就是 Spring 全部支持的切点表达式类型)
            // String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截标有 @AfterTransaction 此注解的任意方法们
    
            // =============================================================
            ProxyFactory factory = new ProxyFactory(new Person());
    
            // 声明一个 aspectj 切点,一张切面
            AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
            cut.setExpression(pointcutExpression); // 设置切点表达式
    
            // 声明一个通知(此处使用环绕通知 MethodInterceptor )
            Advice advice = (MethodInterceptor) invocation -> {
                System.out.println("============>放行前拦截...");
                Object obj = invocation.proceed();
                System.out.println("============>放行后拦截...");
                return obj;
            };
    
            // 切面 = 切点 + 通知
            // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是 Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
            // Pointcut.TRUE:表示啥都返回 true,也就是说这个切面作用于所有的方法上/所有的方法
            // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是 Pointcut.TRUE 切面,因此需要注意这些区别,相当于 new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
            Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
            factory.addAdvisor(advisor);
            Person p = (Person) factory.getProxy();
    
            // 执行方法
            p.run();
            p.run(10);
            p.say();
            p.sayHi("Jack");
            p.say("Tom", 666);
        }
    static class Person { public int run() { System.out.println("我在run..."); return 0; } public void run(int i) { System.out.println("我在run...<" + i + ">"); } public void say() { System.out.println("我在say..."); } public void sayHi(String name) { System.out.println("Hi," + name + ",你好"); } public int say(String name, int i) { System.out.println(name + "----" + i); return 0; } } }

    ProxyFactory

    代理的 Bean 都是 new 出来的,和 Spring 容器没啥关系,可以直接创建代理用

    public class AopTest {
        public static void main(String[] args) {
            ProxyFactory proxyFactory = new ProxyFactory(new MathCalculatorImpl());
    
            // 添加两个 Advise,一个匿名内部类表示
            proxyFactory.addAdvice((AfterReturningAdvice) (returnValue, method, args1, target) ->
                    System.out.println("AfterReturningAdvice method=" + method.getName()));
            proxyFactory.addAdvice(new LogMethodBeforeAdvice());
    
            MathCalculator proxy = (MathCalculator) proxyFactory.getProxy();
            System.out.println(proxy.div(1, 1));
        }
    }

    AspectJProxyFactory

    只需要配置切面、通知、切点表达式就能自动的实现切入的效果,整个代理的过程全部由 Spring 内部完成,无侵入,使用方便,也是当前使用最多的方式

    @Aspect
    static class MyAspect {
        @Pointcut("execution(* div(..))")
        private void beforeAdd() {
        }
    
        @Before("beforeAdd()")
        public void before1() {
            System.out.println("-----------before-----------");
        }
    }
    
    public class AopTest {
        public static void main(String[] args) {
            AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new MathCalculatorImpl());
            // 此处 MyAspect 类上的 @Aspect 注解不能少
            proxyFactory.addAspect(MyAspect.class);
            // proxyFactory.setProxyTargetClass(true); // 是否用 CGLIB 代理
            MathCalculator proxy = proxyFactory.getProxy();
            System.out.println(proxy.div(1, 1));
            System.out.println(proxy.getClass()); // class com.sun.proxy.$Proxy7
        }
    }

    切面类定义中定义了一个 Advisor(必须有 @Aspect 注解标注),其对应了一个 MethodBeforeAdvice,实际上是一个 AspectJMethodBeforeAdvice,该 Advice 对应的是 before1() 方法。

    切面类定义中还定义了一个 Pointcut,是一个 AspectJExpressionPointcut。

    该 Advisor 的语义为拦截所有方法名为 div 的方法,在它之前执行 MyAspect.before1() 方法。

    虽然我们可以自己通过编程的方式使用 AspectjProxyFactory 创建基于 @Aspect 标注的切面类的代理,但是通过配置 <aop:aspectj-autoproxy/>(@EnableAspectJAutoProxy) 使用基于 Aspectj 注解风格的 Aop 时,Spring 内部不是通过 AspectjProxyFactory 创建的代理对象,而是通过 ProxyFactory

    总结

    这三个类本身没有什么关系,但都继承自 ProxyCreatorSupport,创建代理对象的核心逻辑都是在 ProxyCreatorSupport 中实现的

    AspectJProxyFactory、ProxyFactoryBean、ProxyFactory 大体逻辑都是:

    1. 填充 AdvisedSupport(ProxyCreatorSupport 是其子类),然后交给父类 ProxyCreatorSupport。
    2. 得到 JDK 或者 CGLIB 的 AopProxy
    3. 代理调用时候被 invoke 或者 intercept 方法拦截(分别在 JdkDynamicAopProxy 和 ObjenesisCglibAopProxy(CglibAopProxy的子类)中),并且在这两个方法中调用 ProxyCreatorSupport#getInterceptorsAndDynamicInterceptionAdvice 方法去初始化 advice 和各个方法的映射关系,并缓存

    https://blog.csdn.net/f641385712/article/details/88926243

  • 相关阅读:
    【转载】poj 1276 Cash Machine 【凑钱数的问题】【枚举思路 或者 多重背包解决】
    一道蓝桥比赛的训练打印题【构造+不断的构造+构造规律】
    poj 1679 The Unique MST 【次小生成树+100的小数据量】
    poj 2828 Buy Tickets 【买票插队找位置 输出最后的位置序列+线段树】
    【转载】素数快速打表(据说是线性复杂度)
    HDU 1166 敌兵布阵 【线段树-点修改--计算区间和】
    yifan的数组
    Tempter of the Bone
    最短路
    排列2
  • 原文地址:https://www.cnblogs.com/jhxxb/p/14097866.html
Copyright © 2011-2022 走看看