博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于Spring Aop存在的一点问题的思考
阅读量:6162 次
发布时间:2019-06-21

本文共 3833 字,大约阅读时间需要 12 分钟。

hot3.png

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

1. 现象

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

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

       然后是切面类声明如下:

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

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包实验方法如下面这篇博文所示:。这说明这就是切点表达式规定的表示方式,但是本人认为这种方式是不合理的,原因主要有两点:

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

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

你可能感兴趣的文章
Azure: 给 ubuntu 虚机挂载数据盘
查看>>
工作总结 @{var sas = String.Format("{0:yyyy-MM-dd}", Model.DemandTime.GetValueOrDefault());}
查看>>
Bootstrap table分页问题汇总
查看>>
javascript进阶课程--第三章--匿名函数和闭包
查看>>
多线程UI
查看>>
Jenkins部署java项目实例
查看>>
深入理解Python中的yield和send
查看>>
好玩的WPF第四弹:用Viewport2DVisual3D实现3D旋转效果
查看>>
javascript学习笔记
查看>>
VLFeat-----mean sift开源库【配置】【转载】
查看>>
wa,架构师
查看>>
文件打包,下载之使用PHP自带的ZipArchive压缩文件并下载打包好的文件
查看>>
Ioc容器应用浅析
查看>>
把孩子打造成为码农
查看>>
Kinect+OpenNI学习笔记之2(获取kinect的颜色图像和深度图像)
查看>>
垃圾代码评析——关于《C程序设计伴侣》9.4——链表(一)
查看>>
【Ubuntu】在Ubuntu 12.04 LTS上安装JDK6
查看>>
不固定个数的子元素自适应居中
查看>>
黑客新手入门
查看>>
辞职后五险一金怎么处理
查看>>