文档章节

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

爱宝贝丶
 爱宝贝丶
发布于 2018/08/19 18:38
字数 1612
阅读 85
收藏 1

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

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 324
博文 129
码字总数 427858
作品 0
武汉
程序员
私信 提问
最最简单的spring及AOP实例

一、简单的spring实现(annotation方式) bean类 测试类: 运行结果: (xml方式) bean类 xml配置文件applicationContext.xml(放在包com.hello下) 测试类: 二、注解方式实现aop(需要导入...

wangxuwei
2017/10/24
129
0
1000行代码读懂Spring(二)- 在Spring中实现AOP

关于AOP AOP是Spring核心功能之一。今天就用tiny-spring来实现一个AOP。具体功能会包括: 读取AspectJ格式的Pointcut描述。 使用JDK动态代理以及CGLib两种方式进行AOP织入。 AOP分为配置(Po...

黄亿华
2014/01/20
5.7K
3
Spring 2.0 的AOP介绍及其通知类型

Spring 2.0的AOP 在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的J...

小星星程序员
2014/08/15
285
0
【MyBatis源码分析】环境准备

原文出处:五月的仓颉 前言 之前一段时间写了【Spring源码分析】系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章【MyBatis源码分析】,在【MyBat...

五月的仓颉
2017/06/08
0
0
Spring声明式事务配置管理方法

环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可。添加方法: 点击项目右键->Build Path->Add librarys: 打开Add Libraries对话框...

思悟修
2015/04/08
111
0

没有更多内容

加载失败,请刷新页面

加载更多

作为一个(IT)程序员!聊天没有话题?试试这十二种技巧

首先呢?我是一名程序员,经常性和同事没话题。 因为每天都会有自己的任务要做,程序员对于其他行业来说;是相对来说比较忙的。你会经常看到程序员在发呆、调试密密麻麻代码、红色报错发呆;...

小英子wep
52分钟前
10
0
【SpringBoot】产生背景及简介

一、SpringBoot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要...

zw965
今天
4
0
简述并发编程分为三个核心问题:分工、同步、互斥。

总的来说,并发编程可以总结为三个核心问题:分工、同步、互斥。 所谓分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共...

dust8080
今天
6
0
OSChina 周四乱弹 —— 当你简历注水但还是找到了工作

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @花间小酌 :#今日歌曲推荐# 分享成龙的单曲《男儿当自强》。 《男儿当自强》- 成龙 手机党少年们想听歌,请使劲儿戳(这里) @hxg2016 :刚在...

小小编辑
今天
3.2K
22
靠写代码赚钱的一些门路

作者 @mezod 译者 @josephchang10 如今,通过自己的代码去赚钱变得越来越简单,不过对很多人来说依然还是很难,因为他们不知道有哪些门路。 今天给大家分享一个精彩的 GitHub 库,这个库整理...

高级农民工
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部