Spring AOP

Spring AOP

AspectJ5 Pointcut 表达式

语法

表达式 说明
call(Signature) 匹配 Signature 的任何的对于方法或者构造器的调用
execution(Signature) 匹配 Signature 的任何方法或者构造器的执行
get(Signature) 匹配 Signature 的任何的对于字段的引用
set(Signature) 匹配 Signature 的任何的对于字段的赋值
within(TypePattern) every join point from code defined in a type in TypePattern
target(Type or Id) every join point when the target executing object is an instance of Type or Id’s type
args(Type or Id, ...) every join point when the 参数 are instances of Types or the types of the Ids
! Pointcut 不匹配 Pointcut 的
Pointcut0 && Pointcut1 同时符合 Pointcut0 和 Pointcut1
Pointcut0 || Pointcut1 符合 Pointcut0 或者 Pointcut1
  • * 匹配的是任意的字符串序列,但是不会匹配到包的路径里面的 . 这个字符。
  • .. 匹配的是包里面以 . 开始或者结束的任意字符串序列。

示例

  • execution(void Point.setX(int)):Point 类的 setX 方法执行
  • execution(* *(..)):不管方法名是什么、返回类型是什么、参数是什么的任意方法
  • execution(!static * *(..)):不是 static 的任意方法的执行
  • call(void Point.setX(int)):调用了 Point 类的 setX 方法
  • call(* set(..)):不管返回类型是什么、参数是什么的任意 set 方法的任何调用
  • call(*.new(int, int)):对任意构造器的调用,这个构造器接受两个参数
  • call(public * *(..))public 方法的任意调用
  • handler(ArrayOutOfBoundsException):异常执行
  • this(SomeType):当当前正在执行的比如 this 的类型是 SomeType
  • target(SomeType:当目标是 SomeType 类型
  • within(MyClass):当执行的代码属于 MyClass
  • target(Point) && call(int *()):调用 Point 实例的任何一个返回类型是 int 的无参数方法
  • call(* *(..)) && (within(Line) || within(Point)):来自于 Line 或者 Point 的任何方法调用
  • within(*) && execution(*.new(int)):任何拥有一个 int 参数类型的构造器的执行
  • !this(Point) && call(int *(..)):类型不是 Point 的调用返回类型为 int 的方法
  • call(* MyInterface.*(..)):调用 MyInterface 接口里面的任意一个方法

Java 原理

Java 中实现切面,通过JDK 动态代理或者动态字节码技术如 CGLIB实现 AOP。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLIB 做动态代理的。

EnableAspectJAutoProxy

使用 @EnableAspectJAutoProxy 来开启 AOP。

收集切面

首先需要 Spring 能先从所有 Bean 中识别出我们的切面类AnnotationAwareAspectJAutoProxyCreator 类重写的postProcessBeforeInstantiation 方法就是专门处理这个问题的,此操作的要求是必须在其它普通 Bean 对象创建完放入容器前解析

它的父类 AbstractAutoProxyCreator 实现了 BeanPostProcessor

// AbstractAutoProxyCreator.java
Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果已经处理过
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    
    // 无需增强
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }

    // ...

    // 如果存在增强方法,那么创建代理
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        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;
}

getAdvicesAndAdvisorsForBean 主要做的事情如下:

  • (1) 获取所有 beanName
  • (2) 遍历所有 beanName,找出声明了 AspectJ 注解的类
  • (3) 对标记为 AspectJ 注解的类进行增强器的提取
  • (4) 将提取结果加入缓存

参考