8.4.5 基于注解的 零配置 方式 9. 切入点指示符

前面定义切入点表达式时大量使用了execution表达式,其中**execution就是一个切入点指示符**,Spring AOP仅支持部分AspectJ的切入点指示符,但Spring AOP还额外支持一个bean切入点指示符。
不仅如此,因为Spring AOP只支持使用方法调用作为连接点,所以**Spring AOP的切入点指示符仅匹配方法执行的连接点**。
完整的AspectJ切入点语言支持大量的切入点指示符,但是Spring并不支持它们。Spring AOP不支持的切入点指示符有call, getsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@thiswithincode。一旦在Spring AOP中使用这些指示符,将会导致抛出llegalArgumentException异常。

Spring AOP支持的切入点指示符

Spring AOP一共支持如下几种切入点指示符。

execution切入点指示符

execution:用于匹配执行方法的连接点,这是Spring AOP中最主要的切入点指示符。该切入点的用法也相对复杂,

execution表达式的格式

execution表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?
上面格式中的execution是不变的,用于作为execution表达式的开头,整个表达式中各部分的解释如下。

表达式的各组成部分 描述
modifiers-pattern 指定方法的修饰符,支持通配符,该部分可省略。
ret-type-pattern 指定方法的返回值类型,支持通配符,可以使用"*"通配符来匹配所有的返回值类型。
declaring-type-pattern 指定方法所属的类,支持通配符,该部分可省略。
name-pattern 指定匹配指定的方法名,支持通配符,可以使用"*"通配符来匹配所有方法。
param-pattern 指定方法声明中的形参列表.
throws- pattern 指定方法声明抛出的异常,支持通配符,该部分可省略

param-pattern支持的通配符

param-pattern支持两个通配符,即"*"和”..
其中"*"代表个任意类型的参数,而"."代表零个或多个任意类型的参数。
例如:

  • ()匹配了一个不接受任何参数的方法,
  • (..)匹配了一个接受任意数量参数的方法(零个或更多),
  • (*)匹配了一个接受个任何类型参数的方法,
  • (*,String)匹配了接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。

execution表示式示例

例如,如下几个execution表达式:

1
2
3
4
5
6
7
8
//匹配任意public方法的执行
execution(public * * (..))
//匹配任何方法名以"set"开始的方法的执行
execution(* set* (..))
//匹配AccountServicelmpI中任意方法的执行
execution(* org.crazyit.app.service.Impl.AccountServiceImpl.* (..))
//匹配org.crazyit.app.service.Impl包中任意类的任意方法的执行
execution(* org.crazyit.app.service.Impl.*.*(..))

within切入点指示符

within:用于限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点。
例如,如下几个within表达式:

1
2
3
4
// 在org.crazyit.app.service包中的任意连接点(在 Spring AOP中只是方法执行的连接点)
within(org.crazyit.app.service.*)
// 在org.crazyit.app.service包或其子包中的任意连接点(在 Spring AOP中只是方法执行的连接点)
within(org.crazyit.app.service..*)

this切入点指示符

this:用于限定AOP代理必须是指定类型的实例,匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点。
例如,如下this表达式:

1
2
//匹配实现了org.crazyit.app.service.AccountService接口的AOP代理的所有连接点(在Spring AOP中只是方法执行的连接点)
this(org.crazyit.app.service.AccountService)

target切入点指示符

target:用于限定目标对象必须是指定类型的实例,匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点。
例如,如下target表达式

1
2
//匹配实现了`org.crazyit.app.service.AccountService`接口的目标对象的所有连接点
target(org.crazyit.app.service.AccountService)

args切入点指示符

args:用于对连接点的参数类型进行限制,要求参数类型是指定类型的实例。当使用Spring AOP的时候,只能匹配方法执行的连接点

1
2
//匹配只接受一个参数,且传入的参数类型是`Serializable`的所有连接点
args(java.io.Serializable)

该示例中给出的切入点表达式与execution(* * (java.io.Serializable)不同:

  • args版本只匹配动态运行时传入的参数值是Serializable类型的情形;
  • execution版本则匹配方法签名只包含一个Serializable类型的形参的方法

bean切入点指示符

另外, Spring AOP还提供了一个名为bean的切入点指示符,它用于限制只匹配指定Bean实例的连接点。当然, Spring AOP中只能使用方法执行作为连接点。
bean:用于限定只匹配指定Bean实例内的连接点,实际上只能使用方法执行作为连接点。定义bean表达式时需要传入Beanidname,表示只匹配该Bean实例内的连接点。支持使用"*"通配符。
例如,如下几个bean表达式:

1
2
3
4
//匹配 tradeService这个Bean实例内方法执行的连接点
bean(tradeService)
//匹配名字以Service结尾的Bean实例内方法执行的连接点
bean(*Service)

bean切入点表示式是Spring AOP额外支持的

bean切入点表达式是Spring AOP额外支持的,并不是AspectJ所支持的切入点指示符。这个指示符对Spring框架来说非常实用:它可以明确指定为Spring的哪个Bean织入增强处理。

8.4.5 基于注解的 零配置 方式 8. 定义切入点

正如在前面的FourAdviceTest java程序中看到的,这个切面类中定义了4个增强处理,定义4个增强处理时分别指定了相同的切入点表达式,这种做法不太符合软件设计原则:因为如果有一天需要修改该切入点表达式,那就要修改4个地方。

定义切入点

为了解决这个问题, AspectJSpring都允许定义切入点。所谓定义切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。
Spring AOP只支持将Spring Bean方法执行作为连接点,所以可以把切入点看成所有能和切入点表达式匹配的Bean方法
切入点定义包含两个部分

  • 一个切入点表达式。
  • 一个包含名字和任意参数的方法签名。

其中切入点表达式用于指定该切入点和哪些方法进行匹配,包含名字和任意参数的方法签名将作为该切入点的名称。

切入点语法格式

@AspectJ风格的AOP中:

  • 切入点签名采用一个普通的方法定义(方法体通常为空)来提供,且该方法的返回值必须为void;
  • 切入点表达式需要使用@Pointcut注解来标注

下面的代码片段定义了一个切入点:anyOldTransfer,这个切入点将匹配任何名为transfer的方法的执行

1
2
3
4
// 使用`@Pointcut`注解定义切入点
@Pointcut("execution(* transfer(..))")
// 使用一个返回值为`void`、方法体为空的方法来命名 切入点
private void anyOldTransfer(){}

切入点表达式,也就是组成@Pointcut注解的值,是正规的AspectJ切入点表达式。如果想要更多地了解Aspect的切入点语言,请参见AspectJ编程指南。

一旦采用上面的代码片段定义了名为anyOldTransfer的切入点之后,程序就可多次重复使用该切入点了,甚至可以在其他切面类、其他包的切面类里使用该切入点,至于是否可以在其他切面类、其他包的切面类里访问该切入点,则取决于该方法签名前的访问控制符—例如,本示例中anyOldTransfer方法使用private访问控制符,则意味着仅能在当前切面类中使用该切入点。

引入当前切面类中的切入点

如果需要使用本切面类中的切入点,则可在使用@Before@After@Around等注解定义Advice时,使用pointcutvalue属性值引用己有的切入点。例如下面的代码片段:

1
2
3
4
@AfterReturning(
pointcut="myPointcut()",
returning="retVal"
)

从上面代码可以看出,指定切入点时非常像调用Java方法的语法—只是该方法代表一个切入点,其实质是为该增强处理定义一个切入点表达式。

引入其他切面类中的切入点

如果需要使用其他切面类中的切入点,则其他切面类中的切入点不能使用private修饰。而且在使用@Before@After@Around等注解中的pointcutvaue属性值引用已有的切入点时,必须添加类名前缀
下面程序的切面类里仅定义了一个切入点。

1
2
3
4
5
6
7
8
9
10
11
12
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class SystemArchitecture
{
@Pointcut("execution(* org.crazyit.app.service.impl.*.*(..))")
public void myPointcut()
{
}
}

下面的切面类中将直接使用上面定义的myPointcut()切入点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class LogAspect
{
// 直接使用SystemArchitecture切面类的myPointcut()切入点
@AfterReturning(
returning = "rvt",
pointcut = "SystemArchitecture.myPointcut()")
// 声明rvt时指定的类型会限制目标方法必须返回指定类型的值或没有返回值
// 此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
public void log(Object rvt)
{
System.out.println("获取目标方法返回值:" + rvt);
System.out.println("模拟记录日志功能...");
}
}

上面程序中的代码就是直接使用SystemArchitecture类中切入点的代码。当使用其他切面类中的切入点时,应该使用切面类作为前缀来限制切入点。
正如从上面的LogAspect.java中看到的,该类可以直接使用SystemArchitecture类中定义的切入点,这意味着其他切面类也可自由使用SystemArchitecture类中定义的切入点,这就很好地复用了切入点所包含的切入点表达式。

8.4.5 基于注解的 零配置 方式 7. 访问目标方法的参数

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。

JoinPoint类的方法

JoinPoint里包含了如下几个常用的方法。

方法 描述
Object[] getArgs() 返回执行目标方法时的参数。
Signature getSignature() 返回被增强的方法的相关信息
Object getTarget() 返回被织入增强处理的目标对象
Object getThis() 返回AOP框架为目标对象生成的代理对象。

通过使用这些方法就可访问到目标方法的相关信息。

当使用Around增加处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类型是JoinPoint类型的子类。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\JoinPoint
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─FourAdviceTest.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的切面类中定义了BeforeAroundAfterReturningAfer四种增强处理,并分别在4种增强处理中访问被织入增强处理的目标方法执行目标方法的参数访问被织入增强处理的目标对象等。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;
import java.util.Arrays;

// 定义一个切面
@Aspect
public class FourAdviceTest
{
// 定义Around增强处理
@Around("execution(* org.crazyit.app.service.impl.*.*(..))")
public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable
{
System.out.println("Around增强:执行目标方法之前,模拟开始事务...");
// 访问执行目标方法的参数
Object[] args = jp.getArgs();
// 当执行目标方法的参数存在,
// 且第一个参数是字符串参数
if (args != null && args.length > 0
&& args[0].getClass() == String.class)
{
// 修改目标方法调用参数的第一个参数
args[0] = "【增加的前缀】" + args[0];
}
// 执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
System.out.println("Around增强:执行目标方法之后,模拟结束事务...");
// 如果rvt的类型是Integer,将rvt改为它的平方
if (rvt != null && rvt instanceof Integer)
rvt = (Integer) rvt * (Integer) rvt;
return rvt;
}
// 定义Before增强处理
@Before("execution(* org.crazyit.app.service.impl.*.*(..))")
public void authority(JoinPoint jp)
{
System.out.println("Before增强:模拟执行权限检查");
// 返回被织入增强处理的目标方法
System.out.println(
"Before增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
// 访问执行目标方法的参数
System.out
.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("Before增强:被织入增强处理的目标对象为:" + jp.getTarget());
}
// 定义AfterReturning增强处理
@AfterReturning(
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..))",
returning = "rvt"
)
public void log(JoinPoint jp, Object rvt)
{
System.out.println("AfterReturning增强:获取目标方法返回值:" + rvt);
System.out.println("AfterReturning增强:模拟记录日志功能...");
// 返回被织入增强处理的目标方法
System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println(
"AfterReturning增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("AfterReturning增强:被织入增强处理的目标对象为:" + jp.getTarget());
}

// 定义After增强处理
@After("execution(* org.crazyit.app.service.impl.*.*(..))")
public void release(JoinPoint jp)
{
System.out.println("After增强:模拟方法结束后的释放资源...");
// 返回被织入增强处理的目标方法
System.out.println(
"After增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("After增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("After增强:被织入增强处理的目标对象为:" + jp.getTarget());
}
}

从上面的代码可以看出,在BeforeAroundAfterReturningAfter四种增强处理中,其实都可通过相同的代码来访问被增强的目标对象、目标方法和方法的参数,
但只有Around增强处理可以改变方法参数.

被上面切面类处理的目标类还是前面的HellolmplWorldImpl类,主程序获取它们的实例,并执行它们的方法,执行结束将看到如下所示的执行效果:

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
47
48
49
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:addUser
Before增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
执行Hello组件的addUser添加用户:【增加的前缀】孙悟空
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:addUser
After增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
AfterReturning增强:获取目标方法返回值:400
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:addUser
AfterReturning增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:deleteUser
Before增强:目标方法的参数为:[1]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
执行Hello组件的deleteUser删除用户:1
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:deleteUser
After增强:目标方法的参数为:[1]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
AfterReturning增强:获取目标方法返回值:null
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:deleteUser
AfterReturning增强:目标方法的参数为:[1]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:bar
Before增强:目标方法的参数为:[]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad
执行World组件的bar()方法
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:bar
After增强:目标方法的参数为:[]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad
AfterReturning增强:获取目标方法返回值:null
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:bar
AfterReturning增强:目标方法的参数为:[]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad

织入增强处理的顺序

Spring AOP采用和AspectJ一样的优先顺序来织入增强处理:

  • 在”进入“连接点时,具有最高优先级的增强处理将先被织入(所以在给定的两个Before增强处理中,优先级高的那个会先执行)。
  • 在”退出“连接点时,具有最高优先级的增强处理会最后被织入(所以在给定的两个After增强处理中,优先级高的那个会后执行).

##不同切面类的增强处理 默认以随机顺序 织入同一个连接点 ##
当不同切面里的两个增强处理需要在同一个连接点被织入时, Spring AOP将以随机的顺序来织入这两个增强处理。

通过优先级为 不同切面类的增强处理 设置织入顺序

如果应用需要指定不同切面类里增强处理的优先级, Spring提供了如下两种解决方案。

  1. 让切面类实现org.springframework.core.Ordered接口,实现该接口只需实现一个int getOrder()方法,该方法的返回值越小,则优先级越高
  2. 直接使用@Order注解来修饰一个切面类,使用@Order注解时可指定一个int型的value属性,该属性值越小,则优先级越高。

同一个切面类例 同类型的 增强处理 默认以随机顺序织入

同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时, Spring AOP将以随机的顺序来织入这两个增强处理,程序没有办法控制它们的织入顺序。

如果确实需要保证它们以固有的顺序被织入,则可考虑将多个增强处理压缩成一个增强处理;或者将不同的增强处理重构到不同的切面类中,通过在切面类级别上进行排序。

args切入点表达式

如果只需要访问目标方法的参数, Spring还提供了一种更简单的方法:可以在程序中使用args切入点表达式来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Args
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─AccessArgAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面定义一个切面类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AccessArgAspect
{
// 下面的args(arg0,arg1)会限制目标方法必须有2个形参
@AfterReturning(
returning = "rvt",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..)) && args(arg0,arg1)")
// 此处指定arg0、arg1为String类型
// 则args(arg0,arg1)还要求目标方法的两个形参都是String类型
public void access(Object rvt, String arg0, String arg1)
{
System.out.println("调用目标方法第1个参数为:" + arg0);
System.out.println("调用目标方法第2个参数为:" + arg1);
System.out.println("获取目标方法返回值:" + rvt);
System.out.println("模拟记录日志功能...");
}
}

上面程序中的pointcut属性定义了切入点表达式,但该切入点表达式增加了&&args(arg0,arg1)部分,这意味着可以在增强处理方法(access方法)中定义ag0arg1两个形参,这两个形参的类型可以随意指定,但一旦指定了这两个形参的类型,这样两个形参的类型会用于限制目标方法。
例如access方法声明arg0arg1的类型都是String,这会限制目标方法必须带两个String类型的参数
本示例的主程序还是先通过Sprin容器获取HelloImplWorldlmpl两个组件,然后调用这两个组件的方法。编译、运行该程序,将看到如下所示的效果。

1
2
3
4
5
6
7
执行Hello组件的addUser添加用户:孙悟空
调用目标方法第1个参数为:孙悟空
调用目标方法第2个参数为:7788
获取目标方法返回值:20
模拟记录日志功能...
执行Hello组件的deleteUser删除用户:1
执行World组件的bar()方法

从上述运行结果可以看出,使用args表达式有如下两个作用。

  1. 提供了一种简单的方式来访问目标方法的参数
  2. 对切入表达式增加额外的限制。

除此之外,使用args表达式时还可使用如下形式:args(name,age,..),这表明在增强处理方法中可通过nameage来访问目标方法的参数。注意上面arg表达式括号中的两个点,它表示可匹配更多参数——如果该args表达式对应的增强处理方法签名为:

1
2
3
4
5
6
7
@AfterReturning(
returning = "rvt",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..)) && args(name,age,..)")
public void doSomething(String name,int age,Date birth)
{

}

这意味着只要目标方法的第一个参数是String类型,第二个参数是int类型,则该方法就可匹配该切入点。

8.4.5 基于注解的 零配置 方式 6. Around增强处理

@Around注解用于修饰Around增强处理, Around增强处理是功能比较强大的增强处理,它近似等于Before增强处理和AfterReturning增强处理的总和, Around处理既可在执行目标方法之前织入增强动作,也可在执行目标方法之后织入增强动作

Around增强与Before增强和AfterReturning增强的不同

Before增强处理、 AfterReturning增强处理不同的是, Around增强处理可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标方法的执行。
Around增强处理可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。

什么时候适合使用Around增强

Around增强处理的功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before增强处理、Afterreturning增强处理就能解决的问题,则没有必要使用Around增强处理了。
如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around增强处理;尤其是需要改变目标方法的返回值时,则只能使用Around增强处理了

使用Value属性指定被织入的切入点

Around增强处理方法应该使用@Around来标注,使用@Around注解时需要指定一个value属性,该属性指定该增强处理被织入的切入点。

第一个形参必须是ProceedingJoinPoint类型

当定义一个Around增强处理方法时,该方法至少包含一个形参,并且第一个形参必须是ProceedingJoinPoint类型

需要显示调用ProceedingJoinPoint参数的proceed方法来执行目标方法

在增强处理方法体内,调用ProceedingJoinPoint参数的proceed方法才会执行目标方法,这就是Around增强处理可以完全控制目标方法的执行时机、执行方式的关键;
如果程序没有调用ProceedingJoinPoint参数的proceed方法,则目标方法不会被执行。

执行目标方法时传入参数

调用ProceedingJoinPoint参数的proceed方法时,还可以传入一个Object数组作为参数,该数组中的值将被传入目标方法作为执行方法的实参

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Around
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─TxAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的程序定义了一个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
30
31
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;

// 定义一个切面
@Aspect
public class TxAspect
{
// 匹配org.crazyit.app.service.impl包下所有类的、
// 所有方法的执行作为切入点
@Around("execution(* org.crazyit.app.service.impl.*.*(..))")
public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable
{
System.out.println("执行目标方法之前,模拟开始事务...");
// 获取目标方法原始的调用参数
Object[] args = jp.getArgs();
if (args != null && args.length > 1)
{
// 修改目标方法的第一个参数
args[0] = "【增加的前缀】" + args[0];
}
// 以改变后的参数去执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
System.out.println("执行目标方法之后,模拟结束事务...");
// 如果目标方法的返回值rvt的类型是Integer,将rvt改为它的平方
if (rvt != null && rvt instanceof Integer)
rvt = (Integer) rvt * (Integer) rvt;
return rvt;
}
}

上面的程序定义了一个TxAspect切面,该切面里包含一个Around增强处理的processTx()方法,该方法中的代码:Object rvt = jp.proceed(args);用于回调目标方法,回调目标方法时传入了一个args数组,但这个args数组是执行目标方法的原始参数被修改后的结果,这样就实现了对调用参数的修改;rvt = (Integer) rvt * (Integer) rvt;这行代码则用于改变目标方法的返回值。
本示例程序中依然使用前面的Hellolmpl.javaWorld.java类,只是主程序增加了输出addUser()方法返回值的功能。执行主程序,将看到如下效果。

1
2
3
4
5
6
7
8
9
10
11
执行目标方法之前,模拟开始事务...
执行Hello组件的addUser添加用户:【增加的前缀】孙悟空
执行目标方法之后,模拟结束事务...
addUser()的返回值为:400
执行目标方法之前,模拟开始事务...
执行Hello组件的deleteUser删除用户:1
执行目标方法之后,模拟结束事务...
执行目标方法之前,模拟开始事务...
执行World组件的bar()方法
执行目标方法之后,模拟结束事务...

从运行结果可以看出,使用Around增强处理可以取得对目标方法最大的控制权,

  • 既可完全控制目标方法的执行,
  • 也可改变执行目标方法的参数,
  • 还可改变目标方法的返回值。

proceed方法调用目标方法时参数不匹配会出现异常

当调用ProceedingJoinPointproceed方法时,传入的Object数组参数值将作为目标方法的参数,如果传入的Object数组的长度与目标方法所需要参数的个数不相等,或者Object数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。

为了能获取目标方法的参数的个数和类型,需要增强处理方法能访问执行目标方法的参数。

8.4.5 基于注解的 零配置 方式 5. After增强处理

Spring还提供了一个After增强处理,它与AfterReturning增强处理有点相似,但也有区别。

  1. AfterReturning增强处理只有在目标方法成功执行结束后才会被织入
  2. After增强处理不管目标方法如何结束,它都会被织入PS:方法结束包括成功执行结束遇到异常中止这两种情况。

因为不论一个方法是如何结束的, After增强处理都会被织入,因此After增强处理必须准备处理正常返回异常返回两种情况,这种增强处理通常用于释放资源。After增强处理有点类似于finally块。

使用@After注解修饰一个方法,即可将该方法转成After增强处理。使用@After注解时需要指定个value属性,该属性值用于指定该增强处理被织入的切入点,既可是一个已有的切入点,也可直接指定切入点表达式。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\After
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─ReleaseAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的程序将定义一个After增强处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;

// 定义一个切面
@Aspect
public class ReleaseAspect
{
// 匹配org.crazyit.app.service包下所有类的
// 所有方法的执行作为切入点
@After("execution(* org.crazyit.app.service.*.*(..))")
public void release()
{
System.out.println("模拟方法结束后的释放资源...");
}
}

上面程序中的粗体字代码定义了一个After增强处理,不管切入点的目标方法如何结束,该增强处理都会被织入。该示例程序的目标对象依然使用HellolmplWorldImpl类, HelloImpl组件中的deleteUser()方法会因为抛出异常而结束。
主程序依然使用长度-2作为deleteUser()方法的参数,此时将可以看到如图8.3所示的效果。

1
2
3
4
5
6
7
8
9
执行Hello组件的addUser添加用户:悟空
模拟方法结束后的释放资源...
模拟方法结束后的释放资源...
Exception in thread "main" java.lang.IllegalArgumentException: 被删除用户的id不能小于0:-2
at org.crazyit.app.service.impl.HelloImpl.deleteUser(HelloImpl.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
......

After增强特别特别适合用于资源回收

从图8.13中可以看出,虽然deleteUser方法因为legalArgumentException异常结束,但After增强处理依然被正常织入。由此可见,After增强处理的作用非常类似于异常处理中finally块的作用—无论如何,它总会在方法执行结束之后被织入,因此特别适用于进行资源回收。

8.4.5 基于注解的 零配置 方式 4. 定义AfterThrowing增强处理

使用@AfterThrowing注解可修饰AfterThrowing增强处理, AfterThrowing增强处理主要用于处理程序中未处理的异常
使用@AfterThrowing注解时可指定如下两个常用属性。

属性 描述
pointcut或者value 这两个属性的作用是一样的,它们都用于指定该切入点对应的切入表达式。样既可是一个已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后, value属性值将会被覆盖。
throwing 该属性值指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。除此之外,在Advice方法中定义该形参时如果指定了类型,则会限制目标方法必须是抛出该类型的异常的方法

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\AfterThrowing
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─RepairAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的程序定义了一个AfterThrowing增强处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

// 定义一个切面
@Aspect
public class RepairAspect
{
// 匹配org.crazyit.app.service.impl包下所有类的所有方法的执行作为切入点
@AfterThrowing(
throwing = "ex",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..))"
)
// 声明ex时指定的类型会限制目标方法必须抛出指定类型的异常
// 此处将ex的类型声明为Throwable,意味着对目标方法抛出的异常不加限制
public void doRecoveryActions(Throwable ex)
{
System.out.println("目标方法中抛出的异常:" + ex);
System.out.println("模拟Advice对异常的修复...");
}
}

正如在上面的程序中看到的,程序中使用@AfterThrowing注解时指定了一个throwing属性,该属性值为ex,这允许在增强处理方法doRecoveryActions中定义名为ex的形参,程序可通过该形参访问目标方法所抛出的异常
将前面示例中的HelloImpl.java类做一些修改,用于模拟程序抛出异常,修改后的Hellolmpl.java类的代码如下:

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
package org.crazyit.app.service.impl;

import org.springframework.stereotype.Component;

import org.crazyit.app.service.*;

@Component("hello")
public class HelloImpl implements Hello
{
// 定义一个deleteUser方法,模拟应用中删除用户的方法
public void deleteUser(Integer id)
{
if (id < 0)
{
throw new IllegalArgumentException("被删除用户的id不能小于0:" + id);
}
System.out.println("执行Hello组件的deleteUser删除用户:" + id);
}
// 定义一个addUser()方法,模拟应用中的添加用户的方法
public int addUser(String name , String pass)
{
System.out.println("执行Hello组件的addUser添加用户:" + name);
return 20;
}
}

上面程序中的deleteUser()方法可能抛出异常,当调用deleteUser()方法传入的参数小于0时,deleteUser()方法就会抛出异常,且该异常没有被任何程序所处理,故Spring AOP会对该异常进行处理。
该示例的主程序略作改变,将调用deleteUser()方法的参数改为-2。运行该主程序,将看到如下效果。

1
2
3
4
5
6
7
8
执行Hello组件的addUser添加用户:悟空
目标方法中抛出的异常:java.lang.IllegalArgumentException: 被删除用户的id不能小于0:-2
模拟Advice对异常的修复...
Exception in thread "main" java.lang.IllegalArgumentException: 被删除用户的id不能小于0:-2
at org.crazyit.app.service.impl.HelloImpl.deleteUser(HelloImpl.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
...

throwing属性增强处理方法接收异常的参数

可以看到的,@AfterThrowing注解的throwing属性中指定的参数名必须与增强处理方法内的一个形参对应。当目标方法抛出一个未处理的异常时,该异常将会传给增强处理方法对应的参数。

使用throwing属性限定触发的异常类型

使用throwing属性还有一个额外的作用:它可用于限定切入点只匹配指定类型的异常;假如在上面的doRecoveryActions()方法中定义了ex形参的类型是NullPointerException,则该切入点只匹配抛出NullPointerException异常的方法。上面doRecoveryActions方法的ex形参类型是Throwable,这表明该切入点可匹配拋出任何异常的情况。

AOP的AfterThrowing处理与catch语句的不同

AOPAfterThrowing处理虽然可以对目标方法的异常进行处理,但这种处理与直接使用catch捕捉不同:
catch捕捉意味着完全处理该异常,如果catch块中没有重新抛出新异常,则该方法可以正常结束;
AfterThrowing处理虽然处理了该异常,但它不能完全处理该异常,该异常依然会传播到上一级调用者,在本示例程序中异常直接传播到JVM,故导致程序中止。

8.4.5 基于注解的 零配置 方式 3. 定义AfterReturning增强处理

类似于使用@Before注解可修饰Before增强处理,使用@AfterReturning可修饰AfterReturning增强处理, AfterReturning增强处理将在目标方法正常完成后被织入

@AfterReturning注解属性

使用@AfterReturning注解可指定如下两个常用属性。

属性 描述
pointcut或者value 这两个属性的作用是一样的,它们都用于指定该切入点对应的切入表达式。样既可是一个已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后, value属性值将会被覆盖。
returning 该属性值指定一个形参名,用于表示Advice方法中可定义与此同名的形参,**该形参可用于访问目标方法的返回值**。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定具体类型,会限制目标方法必须返回该类型的值

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\AfterReturning
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─LogAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的程序定义了一个AfterReturning增强处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

// 定义一个切面
@Aspect
public class LogAspect
{
// 匹配org.crazyit.app.service.impl包下所有类的、
// 所有方法的执行作为切入点
@AfterReturning(
returning = "rvt",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..))"
)
// 声明rvt时指定的类型会限制目标方法必须返回指定类型的值或没有返回值
// 此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
public void log(Object rvt)
{
System.out.println("获取目标方法返回值:" + rvt);
System.out.println("模拟记录日志功能...");
}
}

正如在上面的程序中看到的,程序中使用@AfterReturning注解时,指定了一个returning属性,该属性值为rvt,这表明允许在Advice方法(log()方法)中定义名为rvt的形参,程序可通过rvt形参来访问目标方法的返回值

运行该应用的主程序,控制台输出如下:

1
2
3
4
5
6
7
8
9
10
执行Hello组件的addUser添加用户:孙悟空
获取目标方法返回值:20
模拟记录日志功能...
执行Hello组件的deleteUser删除用户:1
获取目标方法返回值:null
模拟记录日志功能...
执行World组件的bar()方法
获取目标方法返回值:null
模拟记录日志功能...

returning属性接收目标方法的返回值

@AfterReturning注解的returning属性所指定的形参名必须对应于增强处理中的一个形参名,当目标方法执行返回后,返回值作为相应的参数值传入增强处理方法

returning属性限制切入点只匹配特定返回值的方法

使用returning属性还有一个额外的作用:它可用于限定切入点只匹配具有对应返回值类型的方法,假如在上面的log()方法中定义rvt形参的类型是String,则该切入点只匹配org.crazyit.appservice.impl包下返回值类型为**String的方法或者没有返回值的方法**。当然,上面log()方法的rvt形参的类型是Object,这表明该切入点可匹配任何返回值类型的方法.

AfterReturning增强不能改变目标方法的返回值

注意:虽然AfterReturning增强处理可以访问到目标方法的返回值,但它不可以改变目标方法的返回值

8.4.5 基于注解的 零配置 方式 2. 定义Before增强处理

在一个切面类(用@Aspect修饰的类)里使用@Before来修饰一个方法时,该方法将作为Before增强处理。使用@Before修饰时,通常需要指定一个value属性值,该属性值指定一个切入点表达式,用于指定该增强处理将被织入哪些切入点。(这个切入点表达式既可以是一个已有的切入点,也可以直接定义切入点表达式)

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Before
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─AuthAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的Java类里使用@Before定义了一个Before增强处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 定义一个切面
@Aspect
public class AuthAspect
{
// 匹配org.crazyit.app.service.impl包下所有类的、
// 所有方法的执行作为切入点
@Before("execution(* org.crazyit.app.service.impl.*.*(..))")
public void authority()
{
System.out.println("模拟执行权限检查");
}
}

上面程序使用@Aspect修饰了AuthAspect类,这表明该类是一个切面类,在该切面里定义了一个authority方法—这个方法本来没有任何特殊之处,但因为使用了@Before来标注该方法,这就将该方法转换成了一个Before增强处理
上面程序中使用@Before注解时,直接指定了切入点表达式,指定匹配org.crazyit.app.service.impl包下所有类的所有方法的执行作为切入点。
本应用在org.crazyit.app.service.impl包下定义了两个类:HelloImplWorldImpl,它们与前面介绍AspectJ时所用的两个业务组件类几乎相同(只是增加实现了一个接口),并使用了@Component注解进行修饰。下面是其中HelloImpl类的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.crazyit.app.service.impl;

import org.springframework.stereotype.Component;
import org.crazyit.app.service.*;

@Component("hello")
public class HelloImpl implements Hello
{
// 定义一个deleteUser方法,模拟应用中删除用户的方法
public void deleteUser(Integer id)
{
System.out.println("执行Hello组件的deleteUser删除用户:" + id);
}
// 定义一个addUser()方法,模拟应用中的添加用户的方法
public int addUser(String name, String pass)
{
System.out.println("执行Hello组件的addUser添加用户:" + name);
return 20;
}
}

从上面的HelloImpl类代码来看,它是一个如此”纯净”的Java类,它丝毫不知道将被谁来进行增强,也不知道将被进行怎样的增强——但正因为HelloImpl类的这种”无知”,才是AOP的最大魅力:目标类可以被无限地增强
Spring配置文件中配置自动搜索Bean组件、自动搜索切面类, Spring AOP自动对Bean组件进行增强。下面是Spring配置文件代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 指定自动搜索Bean组件、自动搜索切面类 -->
<context:component-scan
base-package="org.crazyit.app.service,org.crazyit.app.aspect">
<context:include-filter type="annotation"
expression="org.aspectj.lang.annotation.Aspect" />
</context:component-scan>

<!-- 启动@AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>

主程序非常简单,通过Spring容器获取helloword这两个Bean,并调用了这两个Bean的业务方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package lee;

import org.springframework.context.*;
import org.springframework.context.support.*;
import org.crazyit.app.service.*;

public class BeanTest
{
public static void main(String[] args)
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
Hello hello = ctx.getBean("hello", Hello.class);
hello.addUser("孙悟空", "7788");
hello.deleteUser(1);
World world = ctx.getBean("world", World.class);
world.bar();
}
}

执行主程序,将在控制台中看到下所示的效果

1
2
3
4
5
6
模拟执行权限检查
执行Hello组件的addUser添加用户:孙悟空
模拟执行权限检查
执行Hello组件的deleteUser删除用户:1
模拟执行权限检查
执行World组件的bar()方法

使用Before增强处理只能在目标方法执行之前织入增强,如果Before增强处理没有特殊处理,目标方法总会自动执行,如果Before处理需要阻止目标方法的执行,可通过抛出一个异常来实现。**Before增强处理执行时,目标方法还未获得执行的机会,所以Before增强处理无法访问目标方法的返回值**。

8.4.5 基于注解的 零配置 方式 1. 定义切面Bean

当启动了@AspectJ支持后,只要在Spring容器中配置一个带@Aspect注解的Bean,Spring将会自动识别该Bean,并将该Bean作为切面处理。

在Spring容器中配置切面Bean与配置普通Bean一样

Spring容器中配置切面Bean(即带@Aspect注解的Bean)与配置普通Bean没有任何区别,一样使用<bean>元素进行配置,一样支持使用依赖注入来配置属性值;如果启动了Spring的”零配置”特性,一样可以让Spring自动搜索,并加载指定路径下的切面Bean

使用@Aspect标注一个Java类,该Java类将会作为切面Bean,如下面的代码片段所示。

1
2
3
4
5
6
// 使用Aspect定义一个切面类
@Aspect
public class LogAspect
{
//定义该类的其他内容
}

切面类(用@Aspect修饰的类)和其他类一样可以定义方法、定义成员变量,还可能包括切入点、增强处理定义
当使用@Aspect来修饰一个Java类之后, Spring将不会把该Bean当成组件Bean处理,因此负责自动增强的后处理Bean将会略过该Bean,不会对该Bean进行任何增强处理。
开发时无须担心使用@Aspect定义的切面类被增强处理,当Spring容器检测到某个Bean类使用了Aspect修饰之后, Spring容器不会对该Bean类进行增强。

8.4.5 基于注解的”零配置”方式

Spring使用AspectJ注解

AspectJ允许使用注解定义切面切入点增强处理,而Spring框架则可识别并根据这些注解来生成AOP代理。 Spring只是使用了和AspectJ 5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用的是Spring AOP,依然是在运行时动态生成AOP代理,并不依赖于AspectJ的编译器或者织入器
简单地说, Spring依然采用运行时生成动态代理的方式来增强目标对象,所以它不需要増加额外的编译,也不需要AspectJ的织入器攴持;而AspectJ采用编译时增强,所以AspectJ需要使用自己的编译器来编译Java文件,还需要织入器。

Sping中启用@AspectJ支持

在Spring配置文件中开启对@AspectJ切面的支持

为了启用Spring@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中配置如下片段:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="GBK"?>
<!-- 1.在beans上.引入aop -->
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 2.启动@AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>

当然,如果希望完全启动Spring的”零配置”功能,则还需要采用如8.2节所示的方式进行配置。

自动增强

所谓自动增强,指的是Spring会判断一个或多个切面是否需要对指定Bean进行增强,并据此自动生成相应的代理,从而使得增强处理在合适的时候被调用

使用Bean后处理器启用@AspectJ支持

如果不打算使用SpringXML Schema配置方式,则应该在Spring配置文件中增加如下片段来启用@AspectJ支持。

1
2
<!-- 启用@AspectJ支持 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">

上面配置文件中的AnnotationAwareAspectJAutoProxyCreator是一个Bean后处理器,该Bean后处理器将会为容器中符合条件的Bean生成AOP代理。

启用@AspectJ支持需要导入的jar包

为了在Spring应用中启动@AspectJ支持,还需要在应用的类加载路径下增加aspectjweaver.jaraspectjrt.jar这两个JAR库,这两个jar包位于Aspect安装路径下的lib目录中。
除此之外,Spring AOP还需要依赖一个aopalliance.jar.