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

原文地址:https://www.cnblogs.com/jhxxb/p/14097866.html