红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

Spring 学习笔记 8:AOP 实现

2025年6月27日 12点热度 0人点赞 0条评论

通过代理实现 AOP

下面演示怎么通过代理实现 AOP。

需要被 AOP 的类:

interface Hello{
    void sayHello();
    void sayBye();
}
static class Target implements Hello{
​
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
​
    @Override
    public void sayBye() {
        System.out.println("Bye bye");
    }
}

通过代理实现 AOP:

// 切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* sayHello())");
// 通知
MethodInterceptor methodInterceptor = new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before invoke ");
        Object proceed = invocation.proceed();
        System.out.println("after invoke ");
        return proceed;
    }
};
// 切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, methodInterceptor);
// 代理
ProxyFactory proxyFactory = new ProxyFactory();
Target target = new Target();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
Hello hello = (Hello) proxyFactory.getProxy();
System.out.println(hello.getClass());
hello.sayHello();
hello.sayBye();

Spring 框架中切点被抽象为org.springframework.aop.Pointcut接口,AspectJExpressionPointcut是其中一个实现,可以通过切点表达式表示一个切点。

切点表达式的更多说明可以看这里。

org.aopalliance.intercept.MethodInterceptor表示通知,即进入切点时执行的操作,通常在这里进行代码增强和原始方法调用。

Spring 中最基本的切面被抽象成org.springframework.aop.Advisor接口,其中只包含一个切点。DefaultPointcutAdvisor是其一个实现,可以用于定义包含一个切点和一个通知的切面。

ProxyFactory是 Spring 用于创建代理对象的工厂类,可以通过addAdvisor方法添加多个切面,getProxy方法返回具备 AOP 功能的代理对象。

Spring 通过一系列规则决定使用 JDK 或 CGLIB 实现 AOP 代理对象,上面的示例是 CGLIB 实现。

具体来说,ProxyFactory有一个boolean类型的proxyTargetClass属性,如果其为true,使用 CGLIB 代理,如果为false,优先使用 JDK 代理。比如:

ProxyFactory proxyFactory = new ProxyFactory();
Target target = new Target();
proxyFactory.setProxyTargetClass(false);
proxyFactory.setInterfaces(target.getClass().getInterfaces());
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
Hello hello = (Hello) proxyFactory.getProxy();

此时使用 JDK 代理,需要注意的是,JDK 代理需要为ProxyFactory添加代理对象实现的接口类型(setInterfaces),通常是被代理对象实现的所有接口,因此这里是target.getClass().getInterfaces()。

当然,是优先而不是一定,此时如果目标类没有实现接口,或者ProxyFactory没有指定接口,就会使用 CGLIB 实现代理对象。

比如要代理的类型不实现任何接口:

static class Target2{
​
    public void sayHello() {
        System.out.println("Hello World");
    }
​
    public void sayBye() {
        System.out.println("Bye bye");
    }
}

测试用例:

Target2 target = new Target2();
proxyFactory.setProxyTargetClass(false);
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
Target2 hello = (Target2) proxyFactory.getProxy();

实际是使用 CGLIB 实现动态代理。

切点匹配

查看AspectJExpressionPointcut的类图会发现,它实现了一个MethodMatcher接口:

image-20250625192234588

这个接口提供matches方法用于判断切点和指定方法是否匹配。

比如目标类:

static class Target {
    public void sayHello() {
        System.out.println("hello");
    }
​
    public void sayGoodbye() {
        System.out.println("goodbye");
    }
}

测试用例:

// 匹配指定方法
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.demo..sayHello(..))");
Method targetHelloMethod = Target.class.getDeclaredMethod("sayHello");
Method targetGoodbyeMethod = Target.class.getDeclaredMethod("sayGoodbye");
Assertions.assertTrue(pointcut.matches(targetHelloMethod, Target.class));
Assertions.assertFalse(pointcut.matches(targetGoodbyeMethod, Target.class));

除了使用execution(...)匹配方法,还可以用@annotation匹配使用了特定注解的方法,比如目标类:

static class Target2 {
    @Transactional
    public void sayHello() {
        System.out.println("hello");
    }
​
    public void sayGoodbye() {
        System.out.println("goodbye");
    }
}

测试用例:

// 匹配指定注解
Class<?> TestCls = Target2.class;
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
Method targetHelloMethod = TestCls.getDeclaredMethod("sayHello");
Method targetGoodbyeMethod = TestCls.getDeclaredMethod("sayGoodbye");
Assertions.assertTrue(pointcut.matches(targetHelloMethod, TestCls));
Assertions.assertFalse(pointcut.matches(targetGoodbyeMethod, TestCls));

对于某些注解,比如这里的@Transactional,其不仅可以作用于方法,还可以作用于类或接口,如果@Transactional注解出现在类或其基类或实现的接口上,AOP 的代码增强同样应该对其所有公共方法有效。

比如这样的目标类:

@Transactional
static class Target3 {
    public void sayHello() {
        System.out.println("hello");
    }

    public void sayGoodbye() {
        System.out.println("goodbye");
    }
}

或者这样的:

@Transactional
interface TargetInterface{
}

static class Target4 implements TargetInterface{
    public void sayHello() {
        System.out.println("hello");
    }

    public void sayGoodbye() {
        System.out.println("goodbye");
    }
}

可以通过实现一个自定义的MethodMatcher来模拟匹配过程:

static class MyMethodMatcherPointcut extends StaticMethodMatcherPointcut {
    private final Class<? extends Annotation> annotationClazz;

    public MyMethodMatcherPointcut(Class<? extends Annotation> annotationClazz) {
        this.annotationClazz = annotationClazz;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        // 查找方法上有没有注解
        MergedAnnotations annotations = MergedAnnotations.from(method);
        if (annotations.isPresent(annotationClazz)) {
            return true;
        }
        // 查找类、父类、实现的接口上有没有注解
        MergedAnnotations annotations2 = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
        if (annotations2.isPresent(annotationClazz)) {
            return true;
        }
        return false;
    }
}

测试用例:

@Test
public void test3() throws NoSuchMethodException {
    // 匹配指定注解
    MyMethodMatcherPointcut pointcut = new MyMethodMatcherPointcut(Transactional.class);
    assertMethodMatcher(Target.class, pointcut, false, false);
    assertMethodMatcher(Target2.class, pointcut, true, false);
    assertMethodMatcher(Target3.class, pointcut, true, true);
    assertMethodMatcher(Target4.class, pointcut, true, true);
}

private static void assertMethodMatcher(Class<?> TestCls, MethodMatcher methodMatcher,
                                        boolean methodHelloExpire, boolean methodGoodByeExpire) throws NoSuchMethodException {
    Method targetHelloMethod = TestCls.getDeclaredMethod("sayHello");
    Method targetGoodbyeMethod = TestCls.getDeclaredMethod("sayGoodbye");
    Assertions.assertEquals(methodHelloExpire, methodMatcher.matches(targetHelloMethod, TestCls));
    Assertions.assertEquals(methodGoodByeExpire, methodMatcher.matches(targetGoodbyeMethod, TestCls));
}

切面

Spring AOP 中切面分为两种:

  • 由@Aspect定义的高级切面,包含多个切点和通知

  • 由接口Advisor定义的低级切面,只包含一个切点和一个通知

通常开发者只需要操作前者,但实际运行中,Spring 框架会识别高级切面,并将高级切面都拆分成低级切面进行处理。

有这么一个目标类:

static class Hello{
    public void sayHello(){
        System.out.println("Hello");
    }
    public void sayGoodBye(){
        System.out.println("Goodbye");
    }
}

编写一个简单切面对其方法增强:

@Aspect
static class MyAspect{
    @Pointcut("execution(* sayHello())")
    public void sayHelloCall(){}

    @Before("sayHelloCall()")
    public void before(){
        System.out.println("aspect before sayHelloCall");
    }

    @After("sayHelloCall()")
    public void after(){
        System.out.println("aspect after sayHelloCall");
    }
}

这是一个高级切面,包含一个切点和两个通知。

用编程的方式在配置类中定义一个低级切面:

@Configuration
static class MyConfig{
    @Bean
    public DefaultPointcutAdvisor advisor(){
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* sayHello())");
        advisor.setPointcut(pointcut);
        advisor.setAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("advisor before advice");
                Object proceed = invocation.proceed();
                System.out.println("advisor after advice");
                return proceed;
            }
        });
        return advisor;
    }
}

Spring 用于识别和处理 @Aspect 注解的 bean 后处理器是AnnotationAwareAspectJAutoProxyCreator,在容器中注册:

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(MyAspect.class);
ctx.registerBean(MyConfig.class);
ctx.registerBean(ConfigurationClassPostProcessor.class);
ctx.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
ctx.registerBean(Hello.class);
ctx.registerBean(Hello2.class);
ctx.refresh();

可以通过其findCandidateAdvisors方法获取容器中存在的低级切面:

AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = ctx.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
List<Advisor> candidateAdvisors = autoProxyCreator.findCandidateAdvisors();
for (Advisor candidateAdvisor : candidateAdvisors) {
    System.out.println(candidateAdvisor);
}

findCandidateAdvisors方法是protected方法,因此需要将测试类放到同名包下才能调用。

打印:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* sayHello())]; advice [org.springframework.aop.aspectj.annotation.AspectTests$MyConfig$1@3e5499cc]

InstantiationModelAwarePointcutAdvisor: expression [sayHelloCall()]; advice method [public void org.springframework.aop.aspectj.annotation.AspectTests$MyAspect.before()]; perClauseKind=SINGLETON

InstantiationModelAwarePointcutAdvisor: expression [sayHelloCall()]; advice method [public void org.springframework.aop.aspectj.annotation.AspectTests$MyAspect.after()]; perClauseKind=SINGLETON

从打印信息可以很容易看出,第一个切面是自定义的DefaultPointcutAdvisor类型的切面,后两个是从@Aspect定义的高级切面拆分出来的InstantiationModelAwarePointcutAdvisor类型的切面。

当然,此时获取到的Hello类型的 bean 是经过 AOP 增强后的 bean:

Hello hello = ctx.getBean(Hello.class);
hello.sayHello();
hello.sayGoodBye();

执行顺序

当一个方法匹配到多个切面时,存在执行顺序先后的问题,比如在上边的示例中,执行顺序是:

advisor before advice
aspect before sayHelloCall
Hello
aspect after sayHelloCall
advisor after advice

先执行了Advisor切面,再执行@Aspect定义的切面。

可以人为定义切面执行的先后顺序,比如:

@Aspect
@Order(1)
@Component
static class MyAspect{
    // ...
}

这里用@Order注解指定 Aspect 切面的优先级。

  • 注意,这里的@Order注解是一个 Spring 注解,完整类名为org.springframework.core.annotation.Order。Junit 下也存在一个@Order注解,不要混淆。

  • @Order注解的值越小优先级越高。

指定 Advisor 的优先级:

@Configuration
static class MyConfig{
    @Bean
    public DefaultPointcutAdvisor advisor(){
        // ...
        advisor.setOrder(2);
        return advisor;
    }
}

注意,对 bean 方法添加的 Advisor 使用@Order注解是无效的,比如:

@Aspect
@Order(2)
@Component
static class MyAspect{
    // ...
}

@Configuration
static class MyConfig{
    @Order(1)
    @Bean
    public DefaultPointcutAdvisor advisor(){
        // ...
        return advisor;
    }
}

执行结果:

aspect before sayHelloCall
advisor before advice
Hello
advisor after advice
aspect after sayHelloCall
Goodbye

这相当于为 Advisor 设置了最低优先级:

@Order(1)
@Bean
public DefaultPointcutAdvisor advisor(){
    // ...
    advisor.setOrder(Integer.MAX_VALUE);
    return advisor;
}

同一个 Aspect 切面内的通知也存在执行先后顺序的问题:

@Aspect
@Component
static class MyAspect{
	// ...
    @Before("sayHelloCall()")
    public void before1(){
        System.out.println("aspect before1 sayHelloCall");
    }

    @Before("sayHelloCall()")
    public void before2(){
        System.out.println("aspect before2 sayHelloCall");
    }
	// ...
}

执行结果:

aspect before1 sayHelloCall
aspect before2 sayHelloCall

遗憾的是,并不能通过@Order注解人为控制同一个 Aspect 内的多个通知的执行先后顺序:

@Aspect
@Component
static class MyAspect{
	// ...
    @Order(2)
    @Before("sayHelloCall()")
    public void before1(){
        System.out.println("aspect before1 sayHelloCall");
    }

    @Order(1)
    @Before("sayHelloCall()")
    public void before2(){
        System.out.println("aspect before2 sayHelloCall");
    }
	// ...
}

执行结果:

aspect before1 sayHelloCall
aspect before2 sayHelloCall

替代方案是在多个不同切面中定义通知。

工作原理

假设需要使用 AOP 增强的目标类型为:

public static class Hello {
    public void sayHello() {
        System.out.println("Hello");
    }
}

切面:

@Aspect
static class MyAspect {
    @Before("execution(* sayHello())")
    public void before() {
        System.out.println("before advisor");
    }

    @AfterReturning("execution(* sayHello())")
    public void after() {
        System.out.println("after advisor");
    }

    @Around("execution(* sayHello())")
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("around advisor before");
        Object retVal = null;
        try {
            retVal = pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        System.out.println("around advisor after");
        return retVal;
    }
}

前边说了,实际运行中高级切面会被拆分成低级切面,这里用反射来模拟这一过程:

/**
     * 解析 Aspect 定义的切面,返回拆分后的 Advisor 切面集合
     *
     * @param clazz Aspect 类
     * @return Advisor 集合
     */
public static List<Advisor> parseAspect(Class<?> clazz) {
    AspectInstanceFactory aif = new SingletonAspectInstanceFactory(new MyAspect());
    // 检查是否为 @Aspect 定义的切面类
    Aspect aspectAnnotation = clazz.getAnnotation(Aspect.class);
    if (aspectAnnotation == null) {
        throw new RuntimeException("@Aspect annotation not found");
    }
    // 依次解析方法的通知注解,生成对应的 Advisor
    List<Advisor> advisorList = new ArrayList<>();
    for (Method method : clazz.getDeclaredMethods()) {
        Before beforeAnnotation = method.getAnnotation(Before.class);
        AfterReturning afterReturningAnnotation = method.getAnnotation(AfterReturning.class);
        Around aroundAnnotation = method.getAnnotation(Around.class);
        if (beforeAnnotation != null) {
            // 生成前置切面
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(beforeAnnotation.value());
            AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, aif);
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
            advisorList.add(advisor);
        } else if (afterReturningAnnotation != null) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(afterReturningAnnotation.value());
            AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, aif);
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
            advisorList.add(advisor);
        } else if (aroundAnnotation != null) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(aroundAnnotation.value());
            AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, aif);
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
            advisorList.add(advisor);
        } else {
            ;
        }
    }
    return advisorList;
}

可以利用这个工具类获取低级切面:

List<Advisor> advisorList = parseAspect(MyAspect.class);
for (Advisor advisor : advisorList) {
    System.out.println(advisor);
}

创建代理工厂,并添加低级切面:

ProxyFactory proxyFactory = new ProxyFactory();
Hello target = new Hello();
Method targetMethod = target.getClass().getDeclaredMethod("sayHello");
proxyFactory.setTarget(target);
proxyFactory.addAdvisors(advisorList);

这里有三个切面,分别包含三种类型的通知:

  • AspectJAroundAdvice

  • AspectJMethodBeforeAdvice

  • AspectJAfterReturningAdvice

最终调用时,实际执行的是MethodInterceptor这个接口的invoke方法。换言之,要将非MethodInterceptor 类型的通知(AspectJMethodBeforeAdvice和AspectJAfterReturningAdvice)进行转换。

可以通过以下代码模拟转换过程:

/**
     * 获取 Advisor 对应的 MethodInterceptor
     * 如果 Advisor 包含的通知不是 MethodInterceptor 类型,转换
     * @param advisorList Advisor 集合
     * @return MethodInterceptor 集合
     */
public List<MethodInterceptor> exchange(List<Advisor> advisorList) {
    List<MethodInterceptor> methodInterceptorList = new ArrayList<>();
    MethodBeforeAdviceAdapter methodBeforeAdviceAdapter = new MethodBeforeAdviceAdapter();
    AfterReturningAdviceAdapter afterReturningAdviceAdapter = new AfterReturningAdviceAdapter();
    for (Advisor advisor : advisorList) {
        if (methodBeforeAdviceAdapter.supportsAdvice(advisor.getAdvice())) {
            MethodInterceptor interceptor = methodBeforeAdviceAdapter.getInterceptor(advisor);
            methodInterceptorList.add(interceptor);
        } else if (afterReturningAdviceAdapter.supportsAdvice(advisor.getAdvice())) {
            MethodInterceptor interceptor = afterReturningAdviceAdapter.getInterceptor(advisor);
            methodInterceptorList.add(interceptor);
        } else {
            Advice advice = advisor.getAdvice();
            if (!(advice instanceof MethodInterceptor)) {
                throw new RuntimeException("advice is not MethodInterceptor");
            }
            methodInterceptorList.add((MethodInterceptor) advice);
        }
    }
    return methodInterceptorList;
}

这里利用了代理模式,将 Advisor 转换为对应的 MethodInterceptor 类型。

代理工厂提供类似的方法:

// 将非 MethodInterceptor 类型的通知转换为环绕通知
List<Object> interceptors = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(targetMethod, target.getClass());
for (Object interceptor : interceptors) {
    System.out.println(interceptor);
}

有环绕通知后,就可以调用代理方法了。

Hello proxy = (Hello) proxyFactory.getProxy();
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(proxy, target, targetMethod, new Object[0], target.getClass(), interceptors);
// 执行会报错 No MethodInvocation found
// 需要将 methodInvocation 绑定到线程上下文中
methodInvocation.proceed();

所有的环绕通知集合由MethodInvocation接口的proceed依次调用。这里会报错,需要:

// 将 Method Invocation 绑定到线程上下文中
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);

MethodInvocation实际上使用了职责链模式,可以用下面的代码模拟其工作原理:

static class MyMethodInvocation implements ProxyMethodInvocation {
    // 代理对象
    private Object proxy;
    // 代理目标对象
    private Object target;
    // 代理目标方法
    private Method method;
    // 实际参数
    private Object[] args;
    // 增强方法的调用链
    private List<MethodInterceptor> methodInterceptorList;
    // 链式调用的游标,标记当前调用位置
    private int cursor = 0;

    public MyMethodInvocation(Object proxy, Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
        this.target = target;
        this.method = method;
        this.args = args;
        this.methodInterceptorList = methodInterceptorList;
        this.proxy = proxy;
    }

    /**
         * 链式调用增强方法,并最终调用被代理对象
         *
         * @return 返回值
         * @throws Exception
         */
    @Override
    public Object proceed() throws Throwable {
        if (methodInterceptorList == null || methodInterceptorList.isEmpty()
            || cursor >= methodInterceptorList.size()) {
            // 如果没有增强方法调用链或所有增强方法已调用,直接调用原始方法并返回
            return method.invoke(target, args);
        }
        // 调用当前增强方法
        MethodInterceptor methodInterceptor = methodInterceptorList.get(cursor);
        cursor++;
        return methodInterceptor.invoke(this);
    }

    // ...
}

假设有两个MethodInterceptor增强:

static class MethodInterceptor1 implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MethodInterceptor1 before advisor");
        Object result = invocation.proceed();
        System.out.println("MethodInterceptor1 after advisor");
        return result;
    }
}

static class MethodInterceptor2 implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MethodInterceptor2 before advisor");
        Object result = invocation.proceed();
        System.out.println("MethodInterceptor2 after advisor");
        return result;
    }
}

调用:

Hello target = new Hello();
Method method = target.getClass().getDeclaredMethod("sayHello");
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
Object proxy = proxyFactory.getProxy();
List<MethodInterceptor> methodInterceptors = List.of(new MethodInterceptor1(), new MethodInterceptor2());
// 调用代理方法
MyMethodInvocation methodInvocation = new MyMethodInvocation(proxy, target, method, new Object[0], methodInterceptors);
try {
    methodInvocation.proceed();
} catch (Throwable e) {
    throw new RuntimeException(e);
}

这里只是模拟调用过程,这里的自定义类型并不能直接调用 Advisor 转换后的 MethodInterceptor,会报错。

代理创建时机

第一种情况比较简单:

static class Bean1{
    public Bean1() {
        System.out.println("Bean1 constructor");
    }

    @PostConstruct
    public void init() {
        System.out.println("Bean1 @PostConstruct");
    }

    public void hello() {
        System.out.println("Bean1 hello");
    }
}

static class Bean2{
    public Bean2() {
        System.out.println("Bean2 constructor");
    }

    private Bean1 bean1;

    @Autowired
    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    @PostConstruct
    public void init() {
        System.out.println("Bean2 @PostConstruct");
    }
}

Bean1 的 hello 方法会被增强,Bean2 通过方法注入 Bean1。

执行结果:

Bean1 constructor
Bean1 @PostConstruct
..AnnotationAwareAspectJAutoProxyCreator : Creating implicit proxy for bean 'bean1' ...
Bean2 constructor
Bean2 setBean1
Bean2 @PostConstruct

可以看到执行顺序是:

  1. 实例化 Bean1

  2. 调用 Bean1 的@PostConstruct方法初始化对象

  3. 创建 Bean1 的代理对象

  4. 实例化 Bean2

  5. 通过 Setter 方法注入 Bean1 (的代理对象)

  6. 通过 Bean2 的 @PostConstruct方法初始化对象

可见,通常情况下,会在对象初始化结束后,为其创建代理对象。

完整示例见这里。

有种情况比较特殊:

static class Bean1{
    private Bean2 bean2;
    @Autowired
    public void setBean2(Bean2 bean2) {
        System.out.println("Bean1 setBean2");
        this.bean2 = bean2;
    }
    // ...
}

static class Bean2{
    // ...

    private Bean1 bean1;

    @Autowired
    public void setBean1(Bean1 bean1) {
        System.out.println("Bean2 setBean1");
        this.bean1 = bean1;
    }

    // ...

}

Bean1 和 Bean2 互相依赖(循环引用)。

实际执行会看到:

Bean1 constructor
Bean2 constructor
...AnnotationAwareAspectJAutoProxyCreator : Creating implicit proxy for bean 'bean1' ...
Bean2 setBean1
Bean2 @PostConstruct
Bean1 setBean2
Bean1 @PostConstruct

此时的执行顺序是:

  1. 实例化 Bean1

  2. 实例化 Bean2

  3. 创建 Bean1 的代理对象

  4. Bean2 通过 Setter 添加对 Bean1 的依赖。

  5. Bean2 执行@PostConstruct方法初始化。

  6. Bean1 通过 Setter 添加对 Bean2 的依赖。

  7. Bean1 执行 @PostConstruct方法初始化。

可以看到,在这种情况(循环引用)下,不得不在 Bean1 没有完全初始化的情况下提前为其创建代理对象,以便可以在创建 Bean2 后使用代理对象进行依赖注入。

完整示例见这里。

动态通知

我们已经知道了,Spring 框架在实际执行代理方法调用时,使用的是由多个MethodInterceptor组成的调用链。实际上除了基本的 MethodInterceptor,还存在另一种为了处理动态通知的调用类型。

需要被 AOP 增强的目标类:

static class Hello {
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

切面:

@Aspect
static class HelloAspect {
    @Before("execution(* sayHello(..))")
    public void before() {
        System.out.println("Before execution");
    }

    @Before("execution(* sayHello(..)) && args(name)")
    public void before(String name) {
        System.out.println("Before " + name);
    }
}

第一个是前面演示过的基本通知(静态通知),第二个方法则与第一个不同,用表达式args(..)引入了参数,可以在通知中操作调用时实际传入的参数。这种通知被称作“动态通知”。

切点表达式是保存在切点(Pointcut)中的,显然要确定动态通知的参数是原始方法的哪个参数需要借助切点表达式(args(name)),因此动态通知的调用不仅仅要包含一个MethodInterceptor,还包含一个Pointcut。

利用代理工厂获取实际执行代理方法时的调用链:

GenericApplicationContext ctx = new GenericApplicationContext();
// ...
List<Object> methodInterceptors = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(targetMethod, target.getClass());
for (Object methodInterceptor : methodInterceptors) {
    showDetail(methodInterceptor);
}
ctx.close();

showDetail是一个打印信息的工具方法:

public static void showDetail(Object o) {
    try {
        Class<?> clazz = Class.forName("org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher");
        if (clazz.isInstance(o)) {
            Field methodMatcher = clazz.getDeclaredField("matcher");
            methodMatcher.setAccessible(true);
            Field methodInterceptor = clazz.getDeclaredField("interceptor");
            methodInterceptor.setAccessible(true);
            System.out.println("环绕通知和切点:" + o);
            System.out.println("\t切点为:" + methodMatcher.get(o));
            System.out.println("\t通知为:" + methodInterceptor.get(o));
        } else {
            System.out.println("普通环绕通知:" + o);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

打印结果:

普通环绕通知:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@42257bdd
环绕通知和切点:InterceptorAndDynamicMethodMatcher[interceptor=org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@733c423e, matcher=AspectJExpressionPointcut: (java.lang.String name) execution(* sayHello(..)) && args(name)]
	切点为:AspectJExpressionPointcut: (java.lang.String name) execution(* sayHello(..)) && args(name)
	通知为:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@733c423e

可以看到,动态通知的类型为InterceptorAndDynamicMethodMatcher,包含一个切点和一个通知。

实际上InterceptorAndDynamicMethodMatcher是一个 Record 类型:

record InterceptorAndDynamicMethodMatcher(MethodInterceptor interceptor, MethodMatcher matcher) {
}

MethodMatcher代表一个切点,可以利用它获取到切点表达式。

当然,InterceptorAndDynamicMethodMatcher依然可以像MethodInterceptor那样完成链式调用:

MethodInvocation methodInvocation = new ReflectiveMethodInvocation(proxy, target, targetMethod, new Object[]{"Tom"},
                                                                   target.getClass(), methodInterceptors) {
};
methodInvocation.proceed();

完整示例见这里。

本文的完整示例可以从这里获取。

The End.

参考资料

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: aop spring
最后更新:2025年6月27日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号