面向切面的 Spring

常用 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// 启用 AspectJ 自动代理
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)// 自动创建 Spring 的应用上下文
@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> 定义一个切点

面向切面的 Spring
https://blog.yohlj.cn/posts/32c51c28/
作者
Enoch
发布于
2018年1月5日
许可协议