常用 AOP 术语
- 通知(切面的工作被称为通知)。Spring 切面可以应用 5 种类型的通知:
- 前置通知:在目标方法被调用之前调用通知功能;
- 后置通知:在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知:在目标方法成功执行之后调用通知;
- 异常通知:在目标方法抛出异常后调用通知;
- 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
- 连接点:我们的应用可能有数以千计的时机应用通知,这些时机被称为连接点。
- 切点:切点的定影会匹配通知所要织入的一个或多个连接点。
- 切面:切面是通知和节点的集合。
- 引入:引入允许我们向所有的类添加新方法或属性。
- 织入:织入是把切面应用到目标对象并创建新的代理对象的过程。
通过切点来选择连接点
Spring 仅支持 AspectJ 切点指示器的一个子集,Spring AOP 所支持的 AspectJ 切点指示器如下:
- arg() 限制连接点匹配参数为指定类型的执行方法
- @args() 限制连接点匹配参数由指定注解标注的执行方法
- execution() 用于匹配是连接点的执行方法
- this() 限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
- target 限制连接点匹配目标对象为指定类型的类
- @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
- within() 限制连接点匹配指定的类型
- @within() 限制连接点匹配指定注解所标注的类型(当使用 Spring AOP 时,方法定义在由指定的注解所标注的类里)
- @annotation 限定匹配带有指定注解的连接点
主要使用 execution 指示器,可配合其他指示器来限制所匹配的切点。
使用注解创建切面
Spring 使用 AspectJ 注解来声明通知方法:
- @After 通知方法会在目标方法返回或者抛出异常后调用
- @AfterReturning 通知方法会在目标方法返回后调用
- @AfterThrowing 通知方法会在目标方法抛出异常后调用
- @Around 通知方法将目标方法封装起来
- @Before 通知方法会在目标方法调用之前执行
@AspectJ 注解标注一个类为切面。
@Pointcut 注解能够在一个 @AspectJ 切面内定义可重用的切点。
示例代码:
1 2 3 4 5 6 7 8 9 10
| package wiki.hlj.ch4;
public enum Direction {
EAST, SOUTH, WEST, NORTH
}
|
1 2 3 4 5 6 7 8 9 10 11 12
| package wiki.hlj.ch4;
public class Walk {
public Walk() { }
public void go(Direction direction) { System.out.println("Go to the " + direction); }
}
|
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
| package wiki.hlj.ch4;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut;
import java.util.HashMap; import java.util.Map;
@Aspect public class WalkTracker { private Map<Direction, Integer> trackCounts = new HashMap<Direction, Integer>();
@Pointcut("execution (** wiki.hlj.ch4.Walk.go(Direction)) && args(direction)") public void trackWalking(Direction direction) { }
@Before("trackWalking(direction)") public void countWalk(Direction direction) { int currentCount = getWalkCount(direction); trackCounts.put(direction, currentCount + 1); }
public int getWalkCount(Direction direction) { return trackCounts.containsKey(direction) ? trackCounts.get(direction) : 0; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package wiki.hlj.ch4;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration @EnableAspectJAutoProxy public class TrackCounterConfig {
@Bean public Walk walk() { return new Walk(); }
@Bean public WalkTracker walkTracker() { return new WalkTracker(); }
}
|
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
| package wiki.hlj.ch4;
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.springframework.test.util.AssertionErrors.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TrackCounterConfig.class) public class TrackCounterTest {
@Autowired private Walk walk;
@Autowired private WalkTracker walkTracker;
@Test public void testTrackCounter() { walk.go(Direction.NORTH); walk.go(Direction.EAST); walk.go(Direction.NORTH); walk.go(Direction.WEST); walk.go(Direction.NORTH); walk.go(Direction.EAST); walk.go(Direction.SOUTH);
assertEquals("north failed", 3, walkTracker.getWalkCount(Direction.NORTH)); assertEquals("east failed", 2, walkTracker.getWalkCount(Direction.EAST)); assertEquals("west failed", 1, walkTracker.getWalkCount(Direction.WEST)); assertEquals("south failed", 1, walkTracker.getWalkCount(Direction.SOUTH)); }
}
|
在 XML 中声明切面
Spring 的 AOP配置元素:
| AOP 配置元素 |
用途 |
<aop:advisor> |
定义 AOP 通知器 |
<aop:after> |
定义 AOP 后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> |
定义 AOP 返回通知 |
<aop:after-throwing> |
定义 AOP 异常通知 |
<aop:around> |
定义 AOP 环绕通知 |
<aop:aspect> |
定义一个切面 |
<aop:aspectj-autoproxy> |
启用 @AspectJ 注解驱动的切面 |
<aop:before> |
定义一个 AOP 前置通知 |
<aop:config> |
顶层的 AOP 配置元素。大多数的 <aop:*> 元素必须包含在 <aop:config> 元素内 |
<aop:declare-parents> |
以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> |
定义一个切点 |