Spring AOP,从入门到进阶

2022-12-01 21:36:57 浏览数 (1)

Spring AOP,从入门到进阶

Running with Spring Boot v2.4.5, Spring v5.3.6

我们常常在核心业务逻辑中看到诸如事务管理日志记录性能统计等行为,这些行为的代码量一般也就几行,但是却分散在多个类中的多个方法内;这些四处分散的重复代码不仅不利于后期的维护工作,同时也显得核心业务逻辑混乱无章。为了解决这一问题,面向切面编程(Aspect-Oriented Programming)应运而生。不同于面向对象编程(Object-oriented Programming),AOP不再以类(Class)为模块化单元,而是以切面(Aspect)作为模块化单元,也就是通过切面来封装那些四处分散的事务管理、日志记录和性能统计等行为。可能有的人会疑惑,可以将这些行为单独封装起来,并不见得一定要使用AOP啊!别杠,单独封装依然无法保持核心业务逻辑的清清爽爽啊,还是会夹杂在一起,不是吗?顺便提一句,横切关注点(Crosscutting Concern),指的就是事务管理、日志记录和性能统计等行为。

Spring AOPAspectJ是目前Java生态内最受欢迎的两款AOP框架,相较于AspectJ,Spring AOP并没有提供完整的AOP实现,而是侧重于在AOP实现与IOC容器间提供紧密的集成;事实上,Spring AOP适用于企业级开发中绝大多数应用场景,二者简略对比如下表所示:

Spring AOP

AspectJ

运行时织入

不支持运行时织入,仅支持在编译、编译后和类加载期间织入

首先基于目标对象创建代理,然后将切面应用于代理

直接将切面织入到目标对象

仅支持方法级织入

支持字段级、方法级、控制器级织入

1 从实战出发

Spring 1.2为我们带来了第一个版本的Spring AOP框架,很遗憾,该版本的Spring AOP并没有紧跟JDK 1.5的潮流,切面的编写不支持AspectJ注解,使用起来不是那么方便;还好Spring 2.0知耻而后勇,一口气为Spring AOP引入了两种编写切面的方式,分别是AspectJ注解XML配置文件,这两种方式均支持Aspectj切入点表达式其中AspectJ注解是我们常用的风格。值得注意的是Spring 1.2引入的编程式AOP(Programmatic AOP)依然延续至今,并没有被剔除。好了,让我们从实战的角度来体验Spring 1.2和Spring 2.0中Spring AOP框架带给我们的能力吧。

CustomService接口中定义了一个doPrint()方法;CustomServiceImpl类实现了CustomService接口中doPrint()方法,用于将给定内容打印到控制台。CustomService

代码语言:javascript复制
package pers.duxiaotou.service;

public interface CustomService {
    public boolean doPrint(String content);
}

CustomServiceImpl

代码语言:javascript复制
package pers.duxiaotou.service.impl;

import org.springframework.stereotype.Service;
import pers.duxiaotou.service.CustomService;

@Service
public class CustomServiceImpl implements CustomService {
    @Override
    public boolean doPrint(String content) {
        System.out.println(content);
        return true;
    }
}

需求doPrint()方法执行前后分别打印两条日志,日志内容分别为

代码语言:javascript复制
*** start log ***
*** end log ***

1.1 Spring AOP In Spring 1.2

1.1.1 定制化CustomService Bean

CustomServiceBeanPostProcessor

代码语言:javascript复制
package pers.duxiaotou.config.aop;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import pers.duxiaotou.service.CustomService;

@Component
@Slf4j
public class CustomServiceBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (CustomService.class.isAssignableFrom(bean.getClass())
                && "customServiceImpl".equals(beanName)) {
            /*
             * 实现org.springframework.aop.Pointcut接口,声明一个切入点
             */
            Pointcut pointcut = new Pointcut() {
                /*
                 * 判断目标对象是否匹配
                 */
                @Override
                public ClassFilter getClassFilter() {
                    return CustomService.class::isAssignableFrom;
                }
                /*
                 * 判断目标方法是否匹配
                 */
                @Override
                public MethodMatcher getMethodMatcher() {
                    return new MethodMatcher() {
                        @Override
                        public boolean matches(Method method, Class<?> targetClass) {
                            return "doPrint".equals(method.getName());
                        }

                        @Override
                        public boolean isRuntime() {
                            return false;
                        }
                        /*
                         * 若isRuntime()返回true,则根据matches(Method, Class<?>)和matches(Method, Class<?>, Object)进行匹配度校验
                         * 若isRuntime()返回false,则根据matches(Method, Class<?>)进行匹配度校验
                         */
                        @Override
                        public boolean matches(Method method, Class<?> targetClass, Object... args) {
                            return false;
                        }
                    };
                }
            };
            /*
             * 实现org.springframework.aop.MethodBeforeAdvice接口,声明前置通知
             */
            MethodBeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, Object target) throws Throwable {
                    log.trace("*** start log ***");
                }
            };
            /*
             * 实现org.springframework.aop.AfterReturningAdvice接口,声明后置通知
             */
            AfterReturningAdvice afterAdvice = new AfterReturningAdvice() {
                @Override
                public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
                    log.trace("*** end log ***");
                }
            };

            DefaultPointcutAdvisor beforePointcutAdvisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
            DefaultPointcutAdvisor afterPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, afterAdvice);
            List<Advisor> advisors = Arrays.asList(beforePointcutAdvisor, afterPointcutAdvisor);

            TargetSource targetSource = new SingletonTargetSource(bean);

            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.setInterfaces(CustomService.class);
            proxyFactory.setTargetSource(targetSource);
            proxyFactory.addAdvisors(advisors);
            /*
             * 为目标对象CustomService生成代理对象
             */
            return proxyFactory.getProxy();
        }
        return null;
    }
}

顺便提一句,如果觉得分别定义前置通知、后置通知太麻烦,可以直接使用更强大的org.aopalliance.intercept.MethodInterceptor接口,它可以模拟实现:

  • MethodBeforeAdvice
  • AfterReturningAdvice
  • ThrowsAdvice
代码语言:javascript复制
MethodInterceptor aroundAdvice = new MethodInterceptor() {
    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
        log.trace("*** start log ***");
        Object result = invocation.proceed();
        log.trace("*** end log ***");
        return result;
    }
};
1.1.2 测试

DuXiaoTouSpringAopApp

代码语言:javascript复制
package pers.duxiaotou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import pers.duxiaotou.service.CustomService;

@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
@EnableAspectJAutoProxy
public class DuXiaoTouSpringAopApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(DeclarativeSpringAopApp.class, args);
        CustomService customService = applicationContext.getBean(CustomService.class);
        customService.doPrint("spring aop");
    }
}

执行结果

代码语言:javascript复制
2021-05-05 18:41:42.657 TRACE 12132 --- [restartedMain] p.d.c.a.CustomServiceBeanPostProcessor : *** start log ***
spring aop
2021-05-05 18:41:42.658 TRACE 12132 --- [restartedMain] p.d.c.a.CustomServiceBeanPostProcessor : *** end log ***

1.2 Spring AOP In Spring 2.0

1.2.1 启用AspectJ注解主持

如果试图基于Aspect注解来配置Spring AOP,那么需要通过@EnableAspectJAutoProxy注解来开启Spring对AspectJ注解的主持。

注意EnableAspectJAutoProxy注解接口中的proxyTargetClass属性值默认为false,这意味着Spring AOP默认采用JDK动态代理来生成代理对象;如果Spring AOP直接运行在Spring中,那么这句话没毛病;但如果运行在Spring Boot中,那就有问题了,因为Spring Boot中spring.aop.proxy-target-class配置属性的默认值为true,即会强制优先使用CGLIB代理来生成代理对象。

1.2.2 编写AspectJ切入点表达式
1.2.3 使用AspectJ @Pointcut注解声明切入点

CommonPointcutConfig

代码语言:javascript复制
package pers.duxiaotou.config.aop;

import org.aspectj.lang.annotation.Pointcut;

public class CommonPointcutConfig {
    @Pointcut("execution(public * pers.duxiaotou.service.CustomService .*(..))")
    public void customServiceLogPointcut() {};
}
1.2.4 使用AspectJ @Aspect注解声明切面

CommonAdviceConfig

代码语言:javascript复制
package pers.duxiaotou.config.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class CommonAdviceConfig {
    @Before(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceBeforeAdvice(JoinPoint joinPoint) {
        log.trace("*** start log ***");
    }

    @AfterReturning(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceAfterReturningAdvice(JoinPoint joinPoint) {
        log.trace("*** end log ***");
    }
}
1.2.5 测试

DuXiaoTouSpringAopApp

代码语言:javascript复制
package pers.duxiaotou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import pers.duxiaotou.service.CustomService;

@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
@EnableAspectJAutoProxy
public class DuXiaoTouSpringAopApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(DeclarativeSpringAopApp.class, args);
        CustomService customService = applicationContext.getBean(CustomService.class);
        customService.doPrint("spring aop");
    }
}

执行结果

代码语言:javascript复制
2021-05-05 19:03:50.212 TRACE 10984 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** start log ***
spring aop
2021-05-05 19:03:50.216 TRACE 10984 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** end log ***

2. AOP核心术语

下面所列举的AOP核心术语并非Spring AOP独有

术语

描述

Aspect

切面是对横切关注点的模块化抽象;在Spring AOP中,切面由通知和切入点组成。

Joinpoint

连接点一般指一次方法调用、一次异常抛出、甚至是一次变量值的修改;在Spring AOP中,连接点专指方法的执行。

Advice

通知封装了在特定连接点所执行的动作;通知有前置通知、后置通知和环绕通知等类型;包括Spring AOP在内的许多AOP框架都将通知建模为拦截器,并在连接点周围维护了拦截器链。

Pointcut

切入点定义了应该在哪些连接点处应用切面的通知逻辑。

Target object

目标对象指的是需要被增强的对象,在Spring AOP中,目标对象专指需要被代理的业务对象。

Weaving

织入指的是在特定时机将切面应用到目标对象中,这个时机可以为编译时、类加载时和运行时;在Spring AOP中,织入发生在运行时。所谓运行时织入,就是在通过main()方法启动应用程序时,为目标对象生成代理对象,然后将切面连接到代理对象中。

3 AspectJ中5种通知注解在Spring AOP中的执行顺序

模拟方法很简单,只需将@Around@Before@After@AfterReturning@AfterThrowing5种通知逻辑绑定在同一连接点即可。

注意5.2.7版本开始,Spring官方调整了@Around@Before@After@AfterReturning@AfterThrowing这5种通知的执行顺序。

代码语言:javascript复制
package pers.duxiaotou.config.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class CommonAdviceConfig {
    @Before(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceBeforeAdvice(JoinPoint joinPoint) {
        log.trace("*** @Before ***");
    }

    @After(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceAfterAdvice(JoinPoint joinPoint) {
        log.trace("*** @After ***");
    }

    @AfterReturning(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceAfterReturningAdvice(JoinPoint joinPoint) {
        log.trace("*** @AfterReturning ***");
    }

    @Around(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public Object customServiceAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        log.trace("*** @Around before ***");
        Object result = joinPoint.proceed();
        log.trace("*** @Around after ***");
        return result;
    }

    @AfterThrowing(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
    public void customServiceAfterThrowingAdvice(JoinPoint joinPoint) {
        log.trace("*** @AfterThrowing ***");
    }
}

正常执行结果

代码语言:javascript复制
2021-05-06 17:10:27.836 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around before ***
2021-05-06 17:10:27.836 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Before ***
spring aop
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @AfterReturning ***
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @After ***
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around after ***

异常执行结果

代码语言:javascript复制
2021-05-06 17:17:14.710 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around before ***
2021-05-06 17:17:14.710 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Before ***
2021-05-06 17:17:14.716 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @AfterThrowing ***
2021-05-06 17:17:14.716 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @After ***

4 Spring AOP实现原理剖析

聊到Spring AOP的底层实现原理,就不得不提到JDK动态代理CGLIB代理,关于这方面的知识参见之前写的一篇文章《Java动态代理》;既然Spring AOP是基于代理来拓展目标对象的,那就很容易想到:Spring IoC容器内贮存的一定是代理对象而非目标对象,那究竟是如何替换的呢?众所周知,Spring暴露了若干IOC容器拓展点(IoC Container Extensiion Points),BeanPostProcessor接口就是其中之一;有了BeanPostProcessor,任何人都可以在Bean初始化前后对其进行个性化改造,甚至将其替换。

首先,让我们来看一下BeanPostProcessor接口中的内容,它只有两个方法,如下:

代码语言:javascript复制
package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

public interface BeanPostProcessor {
    /**
     * Apply this BeanPostProcessor to the given new bean instance before any bean initialization.
     */
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    /**
     * Apply this BeanPostProcessor to the given new bean instance after any bean initialization.
     */
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

没错,Spring AOP就是通过BeanPostProcessor来将目标对象替换为代理对象的!在Spring AOP中,这个BeanPostProcessor就是AbstractAutoProxyCreator抽象类,其主要用于创建代理对象;Spring AOP为AbstractAutoProxyCreator定义了两个直系子类,分别是:BeanNameAutoProxyCreatorAbstractAdvisorAutoProxyCreator;前者根据Bean的名称来判断是否需要为该Bean创建代理对象,后者根据Advisor探测结果来判断是否需要为该Bean创建代理对象。AbstractAutoProxyCreator的父子关系如下图所示:

AbstractAdvisorAutoProxyCreator是一个抽象类,它有三个子类,其中AnnotationAwareAspectJAutoProxyCreator是我们重点关注的,其可以探测到所有@Aspect注解类,将@Aspect切面类转化为Advisor。

刚才已经提到,AbstractAutoProxyCreator充当了BeanPostProcessor的角色,那其肯定是实现了BeanPostProcessor接口的,如下所示:

代码语言:javascript复制
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);
    
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            // cacheKey一般指的就是beanName
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 根据需要是否为该bean生成代理对象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 如果targetSourcedBeans包含该beanName,则说明该bean的代理对象已经生成完毕,
        // 所以直接返回该bean即可;
        // 在当前类中有两个代理类生成时机,第一个是在bean还没有实例化前;第二个是在
        // bean已经实例化,初始化之后
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        // 如果advisedBeans已存在该cacheKey,则直接返回该bean
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        // AOP基础设施类,指的就是Advice、Pointcut、Advisor和AopInfrastructureBean
        // 接口的实现类;
        // shouldSkip()主要是过滤掉由<aop:config/>标签解析出的AspectJPointcutAdvisor
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            // 将该bean标记为已处理且不需要为其生成代理对象,然后返回该bean
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // 核心逻辑:获取与当前Bean匹配的Advisor列表
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            // 将该Bean标记为已处理且需要为其生成代理对象
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 生成代理对象
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

无疑,AbstractAutoProxyCreator中wrapIfNecessary()方法就是我们进行源码分析的入口了。wrapIfNecessary()方法首先获取与当前Bean匹配的Advisor列表,然后根据所获取的Advisor列表来判断是否需要为该Bean创建代理对象。本文后续重点分析Advisor列表的获取逻辑,而关于代理对象的创建其实很简单,限于篇幅这里就不做分析了。

首先简单介绍下Advisor的概念。Advisor有两个分支,分别是PointcutAdvisorIntroductionAdvisor,本文所指的Advisor列表即PointcutAdvisor列表;Advisor是Spring AOP中独有的术语,在AspectJ中并没有等效的术语与其匹配;但其与切面还是有一定相似之处的,或者大家干脆将其视为一个特殊的切面,该切面只能包含一个通知和一个切入点而已。

代码语言:javascript复制
public interface Advisor {
    Advice getAdvice();
}
public interface PointcutAdvisor extends Advisor {
    Pointcut getPointcut();
}

介绍完了Advisor的概念后,下面就要开始探索Spring AOP究竟是如何获取与当前Bean匹配的Advisor列表了。wrapIfNecessary()方法内通过getAdvicesAndAdvisorsForBean()方法来获取与当前Bean匹配的Advisor列表,其实现如下:

代码语言:javascript复制
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(
            Class<?> beanClass, String beanName, TargetSource targetSource) {
        // Eligible译为合格的,即获取合格的Advisor实例列表
        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
        if (advisors.isEmpty()) {
            return DO_NOT_PROXY;
        }
        return advisors.toArray();
    }
}

getAdvicesAndAdvisorsForBean()方法并没有几行代码,那核心逻辑只能落在findEligibleAdvisors()方法中,其实现如下:

代码语言:javascript复制
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }
}

findEligibleAdvisors()方法极为重要,它主要有三个核心逻辑:

  1. 从当前BeanFactory中获取所有Advisor列表;
  2. 从所获取的Advisor列表中进一步判断哪些Advisor是与当前Bean匹配的,剔除不匹配的;
  3. 在Advisor列表头部追加一个DefaultPointcutAdvisor;

下面分别针对上述三个核心逻辑逐个分析!

4.1 findCandidateAdvisors()

注意子类AnnotationAwareAspectJAutoProxyCreator覆盖了父类AbstractAdvisorAutoProxyCreator的findCandidateAdvisors()方法逻辑。

AnnotationAwareAspectJAutoProxyCreator

代码语言:javascript复制
protected List<Advisor> findCandidateAdvisors() {
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

注意其实上述findCandidateAdvisors()方法第一次执行是由AbstractAutoProxyCreator中postProcessBeforeInstantiation()方法内的shouldSkip()方法触发,即在此时所有Advisor已经获取到了;后期虽然会多次执行findCandidateAdvisors(),但Advisor列表已经被缓存!

super.findCandidateAdvisors()核心实现

代码语言:javascript复制
// 首先,从当前BeanFactory中获取所有Advisor类的名称,生成advisorNames数组         
String[] advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class);
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
    // 然后,根据advisor名称从当前BeanFactory中获取Advisor实例,将其添加到List中
    advisors.add(this.beanFactory.getBean(name, Advisor.class));
}

aspectJAdvisorsBuilder.buildAspectJAdvisors()核心实现

代码语言:javascript复制
// 首先,从当前BeanFactory中获取所有Bean的名称,最终获取到的beanNames数组数量达100 
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class);
List<Advisor> advisors = new ArrayList<>();
for (String beanName : beanNames) {
    Class<?> beanType = this.beanFactory.getType(beanName, false);
    if (beanType == null) {
        continue;
    }
    // 然后,判断当前Bean是否有@Aspect注解
    if (this.advisorFactory.isAspect(beanType)) {
        MetadataAwareAspectInstanceFactory factory =
                new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
        // 最后,将当前Bean内由@Before、@After、@AfterReturning、@Around
        // 和@AfterThrowing注解的方法解析出来,并生成一个Method列表;
        // 再逐个将Method封装为InstantiationModelAwarePointcutAdvisorImpl实例。
        // 另外,InstantiationModelAwarePointcutAdvisorImpl构造方法中有一个方法,即
        // instantiateAdvice(this.declaredPointcut),其主要负责根据切面行为的注解类型
        // 转换为具体的Advice实例,具体的转换规则如下:
        // @Around ---> AspectJAroundAdvice
        // @Before ---> AspectJMethodBeforeAdvice
        // @After ---> AspectJAfterAdvice
        // @AfterReturning ---> AspectJAfterReturningAdvice
        // @AfterThrowing ---> AspectJAfterThrowingAdvice
        List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
        advisors.addAll(classAdvisors);
    }
}

小节总结findCandidateAdvisors()逻辑主要有两点:

  1. 从当前BeanFactory中根据Advisor.class获取Advisor列表,主要是一些自定义Advisor,比如:
代码语言:javascript复制
@Configuration
public class PerformanceAdvisorConfig {
    @Bean
    public PerformanceMonitorInterceptor performanceMonitorInterceptor() {
        return new PerformanceMonitorInterceptor(true);
    }

    @Bean
    public Advisor performanceMonitorAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(public * pers.duxiaotou.service.CustomService .*(..))");
        return new DefaultPointcutAdvisor(pointcut, performanceMonitorInterceptor());
    }
}
  1. 从当前BeanFactory中获取由@Aspect注解的切面,然后解析出切面行为和切入点表达式,最后为每个切面行为生成一个InstantiationModelAwarePointcutAdvisorImpl实例。

4.2 findAdvisorsThatCanApply

现在,我们已经获取到了所有的Advisor列表,但还不知道Advisor可以与哪些Bean匹配。继续分析···

代码语言:javascript复制
protected List<Advisor> findAdvisorsThatCanApply(
        List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}

接下来,进入AopUtils中一探究竟。查阅下面源码后依然没有找到感兴趣的代码。

代码语言:javascript复制
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new ArrayList<>();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor) {
            // already processed
            continue;
        }
        // 执行到这里,应该就是PointcutAdvisor了
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

接下来,进入AopUtils中canApply()寻找答案。好家伙,进来一看,还有一个canApply()的重载方法。

代码语言:javascript复制
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
    // 无需关注IntroductionAdvisor,一般需要重点关注PointcutAdvisor
    if (advisor instanceof IntroductionAdvisor) {
        return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
    }
    else if (advisor instanceof PointcutAdvisor) {
        PointcutAdvisor pca = (PointcutAdvisor) advisor;
        return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
        // It doesn't have a pointcut so we assume it applies.
        return true;
    }
}

再次进入AopUtils中的canApply()重载方法内一探究竟。

代码语言:javascript复制
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    Assert.notNull(pc, "Pointcut must not be null");
    // Pointcut接口实现类需要实现ClassFilter接口和MethodMather接口
    // 首先,根据ClassFilter判断当前切入点是否与目标对象匹配
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }
    // 然后,根据ClassFilter判断当前切入点是否与目标对象所有目标方法匹配
    MethodMatcher methodMatcher = pc.getMethodMatcher();
    if (methodMatcher == MethodMatcher.TRUE) {
        return true;
    }
    // 执行到这里,说明当前切入点不会与目标对象中所有目标方法匹配
    // 下面就要分析究竟是哪些方法匹配了
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    }

    Set<Class<?>> classes = new LinkedHashSet<>();
    // 如果目标对象不是JDK动态代理类
    if (!Proxy.isProxyClass(targetClass)) {
        // 根据targetClass获取用户所定义的原生Class,如CustomServiceImpl Class实例
        classes.add(ClassUtils.getUserClass(targetClass));
    }
    // 获取targetClass所实现的接口,如CustomService Class实例
    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

    for (Class<?> clazz : classes) {
        // 通过反射获取方法列表,包括从父类中继承的方法
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            // 核心逻辑来了,最终通过methodMatcher.matched()方法
            // 来断定切入点与目标对象中哪些目标方法匹配
            if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}

小节总结findAdvisorsThatCanApply()方法的内部核心逻辑主要依托于切入点中ClassFilterMethodMatcher来实现Advisor与Bean的匹配度检测。

4.3 extendAdvisors

extendAdvisors()方法逻辑比较简单,如下:

代码语言:javascript复制
protected void extendAdvisors(List<Advisor> candidateAdvisors) {
    AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
}

继续跟进makeAdvisorChainAspectJCapableIfNecessary()方法,如下:

代码语言:javascript复制
public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
    if (!advisors.isEmpty()) {
        boolean foundAspectJAdvice = false;
        for (Advisor advisor : advisors) {
            // 判断当前Advisor是否包含AspectJ通知
            // 具体地:
            // advisor instanceof InstantiationModelAwarePointcutAdvisor
            // advisor.getAdvice() instanceof AbstractAspectJAdvice
            // advisor instanceof PointcutAdvisor && ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)
            if (isAspectJAdvice(advisor)) {
                foundAspectJAdvice = true;
                break;
            }
        }
        if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
            advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
            return true;
        }
    }
    return false;
}

从源码来看,makeAdvisorChainAspectJCapableIfNecessary()方法只做了一件事,即在当前Advisor列表头部追加一个ExposeInvocationInterceptor.ADVISOR;至于这个ADVISOR的信息只能通过阅读ExposeInvocationInterceptor的源码来获取了,如下:

代码语言:javascript复制
public final class ExposeInvocationInterceptor implements MethodInterceptor {
    
    public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor();
    
    public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) {
        @Override
        public String toString() {
            return org.springframework.aop.interceptor.ExposeInvocationInterceptor.class.getName()  ".ADVISOR";
        }
    };

    private static final ThreadLocal<MethodInvocation> invocation =
            new NamedThreadLocal<>("Current AOP method invocation");
    
    private ExposeInvocationInterceptor() {
    }

    @Override
    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        MethodInvocation oldInvocation = invocation.get();
        invocation.set(mi);
        try {
            return mi.proceed();
        } finally {
            invocation.set(oldInvocation);
        }
    }
}

原来如此,这个ADVISOR指的是DefaultPointcutAdvisor,其所持有的通知就是ExposeInvocationInterceptor;另外,invoke()方法内将MethodInvocation保存在ThreadLocal中,直到整个拦截器链处理完毕才恢复原有的MethodInvocation。

疑问当通过代理对象调用目标方法时,首先会依次执行拦截器链中的拦截器逻辑,然后才是执行目标方法;ExposeInvocationInterceptor有什么特别之处使得它可以坐在拦截器链的第一把交椅呢?

4.4 代理对象中目标方法的执行流程

要想搞清楚代理对象中目标方法的执行流程,首先要找到执行入口。下面分别针对JDK动态代理和CGLIB代理的执行入口进行分析。

4.4.1 JDK动态代理

JDK动态代理是基于接口的实现机制来生成代理对象的,代理对象最终委托java.lang.reflect.InvocationHandler接口中的invoke()方法来进行拓展处理的,换句话说,invoke()方法才是真正的拓展逻辑所在。在Spring AOP中扮演InvocationHandler角色的就是JdkDynamicAopProxy,即JdkDynamicAopProxy实现了InvocationHandler接口,并重写了invoke()方法,具体如下:

代码语言:javascript复制
final class JdkDynamicAopProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object retVal = null;

        // 获取TargetSource,其持有需要被代理对象增强的目标对象
        TargetSource targetSource = this.advised.targetSource;
        // 通过TargetSource获取目标对象
        Object target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            // 如果拦截器链为空,则直接调用目标方法
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        } else {
            // 如果拦截器链非空,则直接执行拦截器链中的逻辑
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }
        return retVal;
    }
}
4.4.2 CGLIB代理

CGLIB代理是基于子类的继承机制来生成代理对象的,代理对象最终委托org.springframework.cglib.proxy.MethodInterceptor接口中的intercept()方法来进行拓展处理的,换句话说,intercept()方法才是真正的拓展逻辑所在。在Spring AOP中扮演MethodInterceptor角色的就是DynamicAdvisedInterceptor,即DynamicAdvisedInterceptor实现了MethodInterceptor接口,并重写了intercept()方法,具体如下:

代码语言:javascript复制
class CglibAopProxy implements AopProxy {
    // CglibAopProxy的静态内部类,DynamicAdvisedInterceptor是最常用的拦截器
    // CglibAopProxy内部还定义了若干其他类型的拦截器
    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        // AOP代理配置管理器,AdvisedSupport实现了ProxyConfig接口
        private final AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object intercept(
                Object proxy,
                Method method,
                Object[] args,
                MethodProxy methodProxy) throws Throwable {
            TargetSource targetSource = this.advised.getTargetSource();
            Object target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);
            // 获取拦截器链
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            Object retVal = null;
            if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                // 如果拦截器链为空,则直接调用目标方法
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = methodProxy.invoke(target, argsToUse);
            } else {
                // 如果拦截器链非空,则直接执行拦截器链中的逻辑
                MethodInvocation invocation =
                        new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);
                // CglibMethodInvocation继承自ReflectiveMethodInvocation
                // CglibMethodInvocation中的proceed()方法实际依然是通过super.proceed()
                // 调用父类ReflectiveMethodInvocation中的proceed()方法
                retVal = invocation.proceed();
            }
            return retVal;
        }
    }
}

JDK动态代理的执行入口与CGLIB代理的执行入口虽然不同,但核心逻辑无疑是一致的:

  1. 获取拦截器链
  2. 依次执行拦截器链中的拦截器逻辑

首先,来看看拦截器链是如何获取到的,一步一步DEBUG,最终发现以下转换规则:

  • ExposeInvocationInterceptor.ADVISOR ---> ExposeInvocationInterceptor
  • DefaultPointcutAdvisor ---> 直接通过Advisor的getAdvice()即可获取到拦截器
  • InstantiationModelAwarePointcutAdvisorImpl ---> 借助AdvisorAdapter将其转换为MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor和ThrowsAdviceInterceptor。

然后,再来看看拦截器链的执行原理。还记得吗?前面在介绍连接点概念的时候,提过一句:包括Spring AOP在内的许多AOP框架都将通知建模为拦截器,并在连接点周围维护了拦截器链。关于“在连接点周围维护了拦截器链”这一点Spring AOP并没有说谎,可以从代码层面来验证;ReflectiveMethodInvocation实现了MethodInvocation接口,而MethodInvocation接口继承自Joinpoint接口,可以说ReflectiveMethodInvocation就是最常用的连接点,查阅其源代码,我们可以发现其维护了一个拦截器链成员变量,这也验证了Spring AOP确实在连接点周围维护了拦截器链。

代码语言:javascript复制
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
    protected final List<?> interceptorsAndDynamicMethodMatchers;

    @Override
    public Object proceed() throws Throwable {
        // 拦截器链处理完毕,则执行目标方法;invokeJoinpoint()内部是通过反射来调用目标方法的
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        // 在将要处理该拦截器链时,会将currentInterceptorIndex自增一
        // 责任链一般都是这种玩法
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(  this.currentInterceptorIndex);
        // 拦截器链中的拦截器都是MethodInteceptor接口的实现类,那肯定自行实现了invoke()方法;
        // 在invoke()方法内部均会有invoke.proceed()的身影,invoke.proceed()方法才是拦截器链不断流转的关键;
        // 无论是JDK动态代理还是CGLIB代理,invoke()方法的参数肯定是同一个MethodInvocation实例;
        // 如果是JDK动态代理,那么这个MethodInvocation实例就是ReflectiveMethodInvocation实例;
        // 如果是CGLIB代理,那么这个MethodInvocation实例就是CglibMethodInvocation实例
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

5 总结

总结的话就不多说了,要不然感觉又能水个三四百字。最后只想分享一个关于切面编写的建议:Pointcut、Advice和Advisor的定义分别放置在独立的配置类中,方便维护。

6 参考文档

  1. https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/core.html#aop
  2. https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/core.html#aop-api
  3. https://www.javachinna.com/logging-performance-monitoring-security-and-transaction-management-with-spring-aop/

0 人点赞