3.3、Spring EL 与 AOP(Aspectj)
3.3.1、Spring 和 AOP的关系
AOP是面向切面编程的简称,Spring的设计思路受到这个思想的指导。所以我们在使用Spring各种组建的时候都能看到这个设计思路的影子。
再举一些实际的例子:我们使用Spring托管hibernate就是一个典型的AOP例子,事务的开启、提交、回滚操作无需业务开发人员进行,全部在业务方法之外自动完成;Spring Cache组件的使用也是一个典型的AOP实例,完成Spring Cache EL的配置后,对Redis/Memcache或者Google Cache的操作完全不需要书写额外代码,全部在业务方法以外完成;而我们之前介绍的Spring中使用的两种代理模式,也是基于AOP思想,无论是JAVA原生动态代理还是Cglib动态代理,都无需业务开发者书写一行额外代码即可完成代理过程。由此可见AOP思想在Spring中的体现是深入的、延续的且广泛的。
上一段文字已经清楚的表明,AOP是一种思想。既然是一种思想就需要具体的手段来进行实现,在Spring中对于AOP思想的实现可以归纳为两种主要手段:
1. 基于JDK动态代理或者Cglib动态代理实现的AOP
实际上本专题之前花了若干篇文章进行介绍的,Spring中基于JDK原生的动态代理和基于Cglib的动态代理实现就是一种实现AOP思想的手段:正常的业务过程方法被代理后,可以在业务执行前、正常执行后、抛出异常后触动一些其它方法过程的执行。
2. 基于AspectJ实现的AOP:
Spring中还集成了一个第三方组件AspectJ,这是一款独立的面向切面框架,Spring在自己的核心组件Spring-aop中对这个第三方组件AspectJ进行了封装(请参见spring-aop组件的源代码)。本篇文章我们主要介绍AspectJ中使用的EL表达式,关于更多AspectJ使用和实现原理的讨论,我们将在后续章节中进行。实际上AspectJ EL表达式不能算作原生的Spring EL范畴(因为它实际上是由AspectJ组件提供的表达式功能),但是目前大家在实际使用时,都将AspectJ当作Spring的一部分,所以在这里本文将AspectJ EL作为一种Spring EL的扩展进行讲解。
3.3.2、 Aspectj EL 示例
AspectJ在Spring的使用方式,主要是两种。一种是在基于XML的Spring配置文件中使用,另一种是基于@AspectJ形式的注解。当然本专题主要基于Spring Boot环境讲解,所以本文主要讲解的还是基于@AspectJ注解的使用形式。首先先介绍AspectJ组件中几种关键的注解形态(它们的定义全部在org.aspectj.lang.annotation包下):
-
@org.aspectj.lang.annotation.Aspect
这个注解只能在Class上进行标注,表示该类作为一个切面定义存在于Spring容器中。 -
@org.aspectj.lang.annotation.Before
该注解只能使用在标注了@Aspect的类的方法上,表示该注解所代表的方法将在符合条件的业务方法(被代理方法)执行前被执行。 -
@org.aspectj.lang.annotation.After
该注解只能使用在标注了@Aspect的类的方法上,表示该注解所代表的方法将在符合条件的业务方法(被代理方法)执行后被执行,无论业务方法是正常返回还是发生异常退出。 -
@org.aspectj.lang.annotation.AfterReturning
该注解只能使用在标注了@Aspect的类的方法上,表示该注解所代表的方法将在符合条件的业务方法(被代理方法)执行后被执行,且只当业务方法正常退出时执行。 -
@org.aspectj.lang.annotation.AfterThrowing
该注解只能使用在标注了@Aspect的类的方法上,表示该注解所代表的方法将在符合条件的业务方法(被代理方法)执行后被执行,且只当业务方法抛出异常退出时执行。 -
@org.aspectj.lang.annotation.Around
该注解只能使用在标注了@Aspect的类的方法上,表示符合条件的业务方法(被代理方法),将视该注解所代表的方法的内部执行过程被动执行,否则就不会执行业务方法(被代理方法)。 -
@org.aspectj.lang.annotation.Pointcut
该注解只能使用在标注了@Aspect的类的方法上,专门用于定义一个可共享的切面执行条件(切面位置)。这样一来保证多个切面点(Before、After、Around等)就可以共享一个切面条件。
3.3.2.1、基本使用
以下我们举例说明一些典型的使用示例。首先,以下代码是一个正常情况下的Spring标准代码,MyService接口中有两个方法,doSomething和doExceptionThing这两个方法分别模拟了一个正常的业务过程和模拟了一个异常的业务过程。代码如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
我们通过JUnit运行对MyService接口的调用,可得到如下结果:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
那么如果我们需要在执行业务方法之前或者之后,或者出现异常等状况下执行一些其它过程,那么我们可以创建一个类似如下的代理器:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
以上示例代码中使用的Aspect相关注解已经说明得比较清楚了,这里的文字就不再进行赘述了。不过注解中的execution表达式要进行一些说明,execution中是一种标准的Aspect EL表达式,这是一种在AspectJ组件中使用的表达式。表达式中“* yinwenjie.test.proxy.service..*.*(..)”字符串的含义是“yinwenjie.test.proxy.service”程序包或者其子包下的任意方法(无论这个方法是否有传参要求,无论是否有返回值,无论方法访问修饰符如何),都使用Aspect相关注解规定的方法被代理。
以下是设定AspectJ注解后再次执行doSomething方法的日志输出效果:
- 1
- 2
- 3
- 4
- 5
- 6
以下是设定AspectJ注解后再次执行doExceptionThing方法的日志输出效果:
- 1
- 2
- 3
- 4
- 5
接着我们可以使用“@Pointcut”注解对相关拦截配置进行设置:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
3.3.2.2、Around注解的使用
那么@Around注解又是怎么使用的呢?@Around注解的作用效果类似于本专题前文介绍过的Cglib动态代理中的MethodInterceptor。如果您使用了@Around注解,那么就需要在使用了@Around注解的方法中手动调用业务方法。如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
这里注意一下,被AspectJ AOP代理的MyService接口实现MyServiceImpl类由于代码没有变化,所以在之上示例中就不再赘述了。以下是使用@Around后的测试效果:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
3.3.3、Aspectj EL 中的表达式
Aspectj EL中的表达式由几个关键字符构成,如下所示:
- “*”该符号是Aspectj EL 中最重要的通配符,可以匹配任意字符,但只限于一个连续的单词。例如在不同语境下,出现一个”*”可以表示一个类名、表示一个方法名或者表示一个包目录的某一层。
- “..”该符号匹配任意字符,并且可以是多个连续的单次。例如在不同语境下,出现”..”可以表示任意深度的包目录、表示方法中任意多个入参类型
- “+”该符号必须伴随类/接口名使用,表示类本身或者继承/实现该类的某个子类。
以下我们给出一些表达式的使用示例,以帮助读者对以上通配符进行理解:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
===============================
(接下文,Aspectj 中的函数、运算符、Aspectj 新特性)