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

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

1. 现象

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

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

       然后是切面类声明如下:

@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配置和驱动类声明:

<?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();
  }
}

       执行结果如下:

before run. 
Tidy is running.
after run.

2. 问题阐述

       这里我们的切点表达式中修饰符使用的是public,而通过Spring的源码可以发现,其是支持多个修饰符的,比如如下的切点表达式:

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

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

3. 原因分析

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

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中声明了,下面是其匹配相关的源码:

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是不允许一个类或方法同时使用两种修饰符的,因而对于上述使用两种修饰符的切点表达式,其将匹配不到任何方法,既然匹配不到任何方法,那为什么还允许这么写呢?

转载于:https://my.oschina.net/zhangxufeng/blog/1930364

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值