关于Spring Aop存在的一点问题的思考

       在本人前面的文章Spring Aop原理之切点表达式解析中讲解了Spring是如何解析切点表达式的,在分析源码的时候,出现了以下将要讲述的问题,我认为是不合理的,后来本人单纯使用aspectj进行试验,发现结果与Spring源码所表现出来的状态是一致的。java

1. 现象

       咱们首先声明一个目标类Dog,其方法执行将会被代理,声明以下:spring

public class Dog {
  public void run() {
    System.out.println("Tidy is running.");
  }
}

       而后是切面类声明以下:app

@Aspect
public class DogAspect {
  @Around("execution(public void Dog.*(..))")
  public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("before run. ");
    Object result = joinPoint.proceed();
    System.out.println("after run.");
    return result;
  }
}

       能够看到,这里DogAspect中声明的切面将会环绕Dog.run()方法的执行。下面是xml配置和驱动类声明:ui

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dog" class="Dog"/>

    <bean id="aspect" class="DogAspect"/>

    <aop:aspectj-autoproxy/>
</beans>
public class DogApp {
  public static void main(String[] args) {
    ApplicationContext context =  new ClassPathXmlApplicationContext("applicationContext.xml");
    Dog dog = context.getBean(Dog.class);
    dog.run();
  }
}

       执行结果以下:this

before run. 
Tidy is running.
after run.

2. 问题阐述

       这里咱们的切点表达式中修饰符使用的是public,而经过Spring的源码能够发现,其是支持多个修饰符的,好比以下的切点表达式:.net

@Around("execution(public protected void Dog.*(..))")

       当使用该切点表达式的时候,上述程序也是能够正常运行的,可是比较奇怪的是,目标方法Dog.run()是没有被代理的。从业务的角度来看,上述表达式理论上应该匹配使用public或者protected修饰的方法,而Dog.run()方法是符合该条件的,可是这里却没有。代理

3. 缘由分析

       这里咱们仍是经过源码来分析上述问题,即当出现多个修饰符的时候Spring是如何对目标方法进行匹配的。以下是Spring Aop对修饰符解析的源码:code

public ModifiersPattern parseModifiersPattern() {
    // 存储修饰符的变量,使用二进制位进行标识
    int requiredFlags = 0;
    // 存储应该被过滤的修饰符,使用二进制位进行标识
    int forbiddenFlags = 0;
    int start;
    while (true) {
        start = tokenSource.getIndex();
        boolean isForbidden = false;
        
        // 若是当前修饰符前面使用!,则表示该修饰符是须要被过滤掉的修饰符
        isForbidden = maybeEat("!");
        // 获取当前的修饰符
        IToken t = tokenSource.next();
        
        // 经过修饰符的名称获取其对应的一个二进制位数据。这里的ModifiersPattern其实比较简单,
        // 你们能够简单的将其理解为一个Map便可,即将每一个修饰符映射到惟一一个二进制位
        int flag = ModifiersPattern.getModifierFlag(t.getString());
        // 若是flag为-1,说明当前字符串不是修饰符,此时退出循环,进行下一步的解析
        if (flag == -1) {
            break;
        }
        
        // 若是当前修饰符是应该被过滤的修饰符,则将其存储在forbiddenFlags中;
        // 若是当前修饰符是被须要的修饰符,则将其存储在requiredFlags中
        if (isForbidden) {
            forbiddenFlags |= flag;
        } else {
            requiredFlags |= flag;
        }
    }

    tokenSource.setIndex(start);
    // 若是被须要的修饰符和被禁止的修饰符都不存在,说明当前切点表达式将匹配以任意类型修饰符修饰的方法
    if (requiredFlags == 0 && forbiddenFlags == 0) {
        return ModifiersPattern.ANY;
    } else {
        // 若是有任意一个值不为0,说明当前切点表达式对修饰符有要求,于是将其封装到ModifiersPattern中
        return new ModifiersPattern(requiredFlags, forbiddenFlags);
    }
}

       能够看到,Spring是将被须要的修饰符和被禁止的修饰符分别存储在两个变量中的:requiredFlags和forbiddenFlags。对于咱们上述声明的两个修饰符public和protected,其对应的flag值分别是1和4。也就是说,此时requiredFlags的值为5,而forbiddenFlags的值为0。这两个值都存储在一个ModifiersPattern类型的对象中。上文中咱们讲过,Spring Aop对目标方法的匹配是经过递归实现的,于是这里对目标方法的匹配逻辑确定是在ModifiersPattern中声明了,下面是其匹配相关的源码:xml

public class ModifiersPattern extends PatternNode {
	private int requiredModifiers;
	private int forbiddenModifiers;

	public ModifiersPattern(int requiredModifiers, int forbiddenModifiers) {
		this.requiredModifiers = requiredModifiers;
		this.forbiddenModifiers = forbiddenModifiers;
	}
    
	public boolean matches(int modifiers) {
		return ((modifiers & requiredModifiers) == requiredModifiers) 
            && ((modifiers & forbiddenModifiers) == 0);
	}
}

       能够看到,ModifiersPattern.matches()就是其匹配逻辑所在,参数modifiers就是目标方法的修饰符。在其实现逻辑中,与requiredModifiers相关的代码能够看出,若是在切点表达式中声明了两个修饰符,那么要求目标方法的修饰符也必须是至少包含这两个。对于这里的例子也就是说,目标方法必须使用至少public和protected进行修饰。这就是问题的所在,理论上Java是不容许方法拥有两个修饰符的,也就是说这里切点表达式是不管如何都没法匹配上任何方法的。对象

4. 我的观点

       本人开始觉得上述问题是Spring产生的bug,后来查阅了相关文档,暂时没发现对上述问题有描述的文档。后来本人单纯使用aspectj的jar包进行实验,发现结果是一致的,使用aspectj的jar包实验方法以下面这篇博文所示:AspectJ——简介以及在IntelliJ IDEA下的配置。这说明这就是切点表达式规定的表示方式,可是本人认为这种方式是不合理的,缘由主要有两点:

  • 从用户的角度来说,当切点表达式中使用了两个修饰符时,通常的思考方向就是这种写法应该是或的关系,即将匹配使用其中任意一种修饰符的目标对象;
  • 从Java语法的角度来说,Java是不容许一个类或方法同时使用两种修饰符的,于是对于上述使用两种修饰符的切点表达式,其将匹配不到任何方法,既然匹配不到任何方法,那为何还容许这么写呢?
相关文章
相关标签/搜索