1. 引入aspectjweaver依赖.
aspectj
aspectjweaver
1.5.4
2. 开启@AspectJ支持
2.1 java配置方式
@EnableAspectJAutoProxy
2.2 XML方式
注: 在xml配置中, 要提供aop的命名空间,即引入spring-aop
schema.
3. 声明切面
创建一个类, 在类中定义切面逻辑.
3.1 java方式
在常规类上添加@Aspect
注解, 由于组件扫描不能自动发现这个注解, 所以还要加上@Component
注解.
3.2 xml方式
- 将常规类声明为一个bean.
- 将这个bean声明为一个切面
...
注:所有的aspect
元素都必须声明在
元素之内.
4. 声明切入点
在spring aop中, 切入点是两部分组成的:
- 一是在切面中定义的一个方法, 且方法的返回值类型必须是
void
.方法可以有参数, 方法体为空(仅为切入点时). - 二是切入点表达式.具体的表达式定义见
附录一
.
4.1 java方式定义切入点表达式
使用@Pointcut
注解
注: java配置方式中可以使用&&, ||, !
来组合多个切点表达式.
4.2 xml方式定义切入点表达式
- 在
元素中定义expression
属性. 可以作为
的子元素定义, 也可以作为
的子元素定义.
// 作为的子元素定义
// 在引用时通过通知元素的pointcut-ref属性引用这个切入点的id
// 也可以在元素中定义它并通过与上面相同的方式引用它.
注1:表达式中可用and, or, not
来代替&&, ||, !
注2:xml配置中不能使用复合切入点(即不能将多个单独的切入点组合在一起使用).
5. 声明通知
通知的作用是在切入点处添加额外的业务逻辑.因此它是与切入点相关联的.
5.1 通知类型
通知有5种类型, 可以借用try-catch-finally模型来理解, 具体见附录二.
5.2 java方式定义
在通知注解中引入切入点, 也可在通知注解中直接使用切入点表达式.
- @Before
- @After
- @AfterReturning
可以通过returning
属性定义返回值的参数名称, 在通知方法参数中用这个参数名接收返回值. - @AfterThrowing
可以通过throwing
属性定义异常参数, 在通知方法参数中定义可匹配指定的异常类型. - @Around
通知方法的第一个参数必须是ProceedingJoinPoint
类型, 调用它的proceed()
方法会执行切入点方法.可以不调用, 也可以调用多次
5.3 xml方式定义
通知是作为
元素的子元素定义的, 通过pointcut
属性定义切入点表达式或通过pointcut-ref
引用定义好的切入点.通过method
属性指定通知方法.通过arg-names
属性指定通知参数.
- before
- afterReturning
- afterThrowing
- after
- around
6. 通知参数
6.1 JoinPoint
任何通知都可以声明`org.aspectj.lang.JoinPoint类型的参数作为它的第一个参数.
这个类提供了一些方法:
- getArgs(): 返回连接点方法的参数值.
- getThis(): 返回代理对象
- getTarget(): 返回目标对象
- getSignature(): 返回切入点方法签名
- toString(): 返回切入点表达式
6.2 绑定参数
在通知定义中使用某些相关的参数.
- 通过args绑定
a. 在args标识符中定义参数名称, 使用..
表示任何参数.
b. 在切入点签名中定义参数类型
c. 在通知方法参数中定义相关的参数类型
@Pointcut("args(p)") // 定义参数名
public void insert(Person p) { // 定义参数类型
}
@Before("insert(p)") // 引用切入点
public void before(Person p) { // 切入点定义的参数
System.out.println(p.getName()); // 使用参数
System.out.println("==========before=========");
}
this, target, @within, @target, @annotation, @args也可用上面类似的方式来绑定参数.
绑定泛型参数
只需要在切入点定义中指定具体的参数类型即可.
注: 这对集合类型的泛型是不能像上面那样定义的, 因为此时spring aop不会去检查集合中元素的类型,即无论你的集合定义的是何种具体类型,都会得到同样的结果, 如果要使用其正常工作, 只能用collection>
来接收参数, 然后手动检查集合中数据的类型.关于参数名称
aop默认是使用相关表达式中定义的参数, 可以通过argNames
属性显示指定参数名称, 也必须也表达式中定义的一致.
6.3 通知的执行顺序
在同一个连接点执行多个通知, 可通过@Order
注解或实现Ordered
接口, 定义的值
小的拥有相对高的优先级, 对于before通知, 最高优先级的通知最先执行, 对于after类的通知, 则是优先级最高的最后执行.
7. 引入
所谓引入
就是给一个类添加新的方法或字段.即等于在一个类中添加一些属性或方法. 这通常是在为一些不能修改或不便于修改的类添加新的逻辑.
7.1 步骤如下:
- 定义一个接口
- 定义这个接口的实现类
- 定义切面
java配置方式:
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
xml配置方式:
附录:
一. 切入点表达式
1. 切点标识符
标识符 | 说明 |
---|---|
execution | 方法级别(连接点)匹配 |
within | 类级别匹配,注意不能匹配与切面同包中的类 |
this | 匹配到具体的某个类或接口(代理对象) |
target | 匹配到具体的某个类或接口(目标对象) |
args | 匹配到方法的参数类型 |
@target | 目标对象所在类上具有给定的注解且注解的Retention为RUNTIME, 如果为CLASS则会抛出异常 |
@within | 类上有指定类型的注解, 以上两种都可以 |
@args | 指参数所在的类上具有指定类型的注解 |
@annotation | 连接点方法上具有指定类型的注解 |
bean | 通过id或名称指定的bean |
以上标识符中都可以使用*
作为通配符
2. 表达式格式
- execution
execution(modifiers? ret-type declaring-type?method-name(param) throws-exp?)
// 带?部分为可选部分
modifiers: 方法修饰符, public, private, protected
. 可选.
ret-type: 方法返回值类型, 必需.
declaring-type: 方法所在的类的全限定类名称, 可选.
method-name: 方法名, 必需.
param: 参数
a. (): 表示无参.
b. (..): 表示任何参数(0个或多个, 任意类型).
c. (): 表示一个参数(任意类型).
d. (, String): 第一个参数为任意参数, 第二参数必须是String
类型.
throws-exp: 表示方法抛出的异常类型(必须是全限定类名称), 可选.
以上各部分都可用*
通配符进行全部可部分匹配.对于类型最好用全限定类名(可控).
// 匹配任意的public方法
execution(public * *(..))
// 匹配以set开头的方法
execution(* set*(..))
// 匹配在com.xyz.service.AccountService接口中定义的任何方法
execution(* com.xyz.service.AccountService.*(..))
// 匹配在com.xyz.service包中定义的任何方法
execution(* com.xyz.service.*.*(..))
// 匹配在com.xyz.service包及其子包中的任何方法
execution(* com.xyz.service..*.*(..))
- within
// 匹配com.xyz.service包中的任何方法
within(com.xyz.service.*)
// 匹配com.xyz.service包及其子包中的方法
within(com.xyz.service..*)
- this
// 代理对象实现了com.xyz.service.AccountService接口
this(com.xyz.service.AccountService)
- target
// 目标对象实现了com.xyz.service.AccountService接口
target(com.xyz.service.AccountService)
- args
// 只有一个参数且参数的类实现了Serializable接口
args(java.io.Serializable)
- @target
// 目标对象上有@Transactional注解
@target(org.springframework.transaction.annotation.Transactional)
- @within
// 目标对象上有@Transactional注解
@within(org.springframework.transaction.annotation.Transactional)
- @annotation
// 方法上有@Transactional注解
@annotation(org.springframework.transaction.annotation.Transactional)
- @args
// 参数类型上有@Classified注解
@args(com.xyz.security.Classified)
- bean
// tradeService bean
bean(tradeService)
// 匹配所有以Service结尾的bean
bean(*Service)
二. 通知类型
- 通知类型
类型 | 说明 |
---|---|
before | 前置通知, 在进入切入点之前执行 |
afterReturning | 后置返回通知, 在切入点正常执行完成后执行 |
afterThrowing | 后置异常通知, 在切入点执行时抛出异常时执行 |
after | 后置通知,无论切入点是否正常执行完, 都会执行. |
around | 环绕通知, 可在切入点前后都执行. |