Spring:AOP 面向切面编程 | BNDong
0%

Spring:AOP 面向切面编程

AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

AOP 是软件开发思想阶段性的产物,我们比较熟悉面向过程 OPP 和面向对象 OOP,AOP 是 OOP 的延续,但不是 OOP 的替代,而是作为 OOP 的有益补充。

参考《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的AOP章节和其他资料将其知识点整理起来。

部分代码实例摘自《精通Spring4.x 企业应用开发实战》,文末我会给出两本书的PDF下载地址!

AOP概述

AOP为 Aspect Oriented Programming 的缩写,意为:面向切面编程。那么什么又是面向切面呢?

我知道面向对象的特性:封装、继承、多态。通过抽象出代码中共有特性来优化编程,但是这种方式又往往不能完全适用任何场景,无法避免的造成代码之间的耦合。

如下面的代码,运用的OOP思想将共有操作抽象了出来,抽象出了两个处理类:性能监视 PerformanceMonitor 和事务管理 TransactionManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.smart.concept;

public class ForumService {
private TransactionManager transManager;
private PerformanceMonitor pmonitor;
private TopicDao topicDao;
private ForumDao forumDao;

public void removeTopic(int topicId) {
pmonitor.start();
transManager.beginTransaction();
topicDao.removeTopic(topicId); // ①
transManager.commit();
pmonitor.end();
}

public void createForum(Forum forum) {
pmonitor.start();
transManager.beginTransaction();
forumDao.create(forum); // ②
transManager.commit();
pmonitor.end();
}
}

①、② 处是两个方法 removeTopiccreateForum 独有的业务逻辑,但它们淹没在了重复化非业务性代码之中。这种抽象为纵向抽取。

removeTopiccreateForum 进行横切重新审视:

我们无法通过纵向抽取的方式来消除代码的重复性。然而切面能帮助我们模块化横切关注点,横切关注点可以被描述为影响应用多处的功能。例如,性能监视和事务管理分别就是一个横切关注点。

AOP 提供了取代继承和委托的另一种可选方案,那就是横向抽取,可以在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。

AOP术语

AOP 不容易理解的一方面原因就是概念太多,并且由英语直译过来的名称也不统一,这里我选择使用较多的译名并给出英文供大家参考。

连接点(Joinpoint)

程序执行的某个时间点,如类初始化前/后,某个方法执行前/后,抛出异常前/后。

Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时这些程序执行点织入增强。

切点(Poincut)

一个程序类可以有多个连接点,但是如果某一部分连接点需要用什么来定位呢?那就是切点,这么说可能有点抽象。借助数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.

增强/通知(Advice)

将一段执行逻辑添加到切点,并结合切点信息定位到具体的连接点,通过拦截来执行逻辑。

Spring 切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

目标对象(Target)

增强逻辑的织入目标对象。目标对象也被称为 advised object

因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object) 注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.

织入(Weaving)

将增强添加到目标类的具体连接点上的过程。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。
  • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

引介/引入(Introduction)

向现有的类添加新的方法或属性。

Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现。

代理(Proxy)

一个类被 AOP 织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以可以采用与调用原类相同的方式调用代理类。

在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象.

切面(Aspect)

切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中。

Spring对AOP的支持

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP;
  • 纯POJO切面;
  • @AspectJ 注解驱动的切面;
  • 注入式AspectJ切面(适用于Spring各版本)。

Spring AOP原理

Spring AOP 的底层原理就是动态代理

Java 实现动态代理的方式有两种:

  • 基于 JDK 的动态代理。
  • 基于 CGLib 的动态代理。

JDK 动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了。

  • CGLib 代理其生成的动态代理对象是目标类的子类。
  • Spring AOP 默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理

那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:

  • 如果是单例的我们最好使用 CGLib 代理,如果是多例的我们最好使用 JDK 代理

原因:

  • JDK 在创建代理对象时的性能要高于 CGLib 代理,而生成代理对象的运行性能却比 CGLib 的低。
  • 如果是单例的代理,推荐使用 CGLib

看到这里我们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能

  • 这样一来,我们就在写业务时只关心业务代码,而不用关心与业务无关的代码

代码实例

带有横切逻辑的实例

ForumService.java

1
2
3
4
5
6
package com.spring05;

interface ForumService {
public void removeTopic(int topicId);
public void removeForum(int forumId);
}

ForumServiceImpl.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
26
27
28
29
30
31
32
33
package com.spring05;

public class ForumServiceImpl implements ForumService{
@Override
public void removeTopic(int topicId) {

// 开始对该方法进行性能监视
PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic");
System.out.println("模拟删除Topic记录:"+topicId);
try {
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束对该方法的性能监视
PerformanceMonitor.end();
}

@Override
public void removeForum(int forumId) {

// 开始对该方法进行性能监视
PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum");
System.out.println("模拟删除Forum记录:"+forumId);
try {
Thread.currentThread().sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束对该方法的性能监视
PerformanceMonitor.end();
}
}

MethodPerformance.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring05;

public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;

public MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis(); // 记录目标类方法开始执行点的系统时间
}

public void printPerformance() {
this.end = System.currentTimeMillis(); // 获取目标类方式执行完成后的系统时间,进而计算出目标类方法的执行时间
long elapse = end - begin;
System.out.println(serviceMethod + "花费" + elapse + "毫秒");
}
}

PerformanceMonitor.java

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

public class PerformanceMonitor {
// 通过一个ThreadLocal保存与调用线程相关的性能监视信息
private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();

// 启动对某一目标方法的性能监视
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}

public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();

// 打印出方法性能监视的结果信息
mp.printPerformance();
}
}

TestForumService.java

1
2
3
4
5
6
7
8
9
package com.spring05;

public class TestForumService {
public static void main(String[] args) {
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
}
}

执行 TestForumService.main 输出结果:

1
2
3
4
5
6
7
8
begin monitor...
模拟删除Forum记录:10
end monitor...
com.spring05.ForumServiceImpl.removeForum花费42毫秒
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.spring05.ForumServiceImpl.removeTopic花费28毫秒

ForumServiceImpl 中的方法中仍然有非业务逻辑的性能监视代码(每个业务方法都有性能监视的开启和关闭代码),这破坏了方法的纯粹性。下面通过 JDK 动态代理和 CGLib 动态代理使非业务逻辑的性能监视代码动态的织入目标方法,以优化代码结构。

PS:上面代码可以拷贝出一份,下面举例大多都是以上面代码为基础改进的。

JDK动态代理

先注释掉 ForumServiceImpl 中非业务逻辑的代码:

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
package com.spring06;

public class ForumServiceImpl implements ForumService{
@Override
public void removeTopic(int topicId) {

// 开始对该方法进行性能监视
// PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeTopic");
System.out.println("模拟删除Topic记录:"+topicId);
try {
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束对该方法的性能监视
// PerformanceMonitor.end();
}

@Override
public void removeForum(int forumId) {

// 开始对该方法进行性能监视
// PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeForum");
System.out.println("模拟删除Forum记录:"+forumId);
try {
Thread.currentThread().sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束对该方法的性能监视
// PerformanceMonitor.end();
}
}

创建横切代码处理类:

PerformanceHandler.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
26
27
package com.spring06;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* 横切代码处理类,实现InvocationHandler接口
*/
public class PerformanceHandler implements InvocationHandler {
private Object target;

public PerformanceHandler(Object target) {
// 设置目标业务类
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 开始性能监视
PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
// 通过反射机制调用目标对象方法
Object obj = method.invoke(target, args);
// 结束性能监视
PerformanceMonitor.end();
return obj;
}
}

修改 TestForumService 调用流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.spring06;

import java.lang.reflect.Proxy;

public class TestForumService {
public static void main(String[] args) {

// 希望被代理的目标业务类
ForumService target = new ForumServiceImpl();

// 将目标业务类和横切代码编织到一起
PerformanceHandler handler = new PerformanceHandler(target);

// 根据编织了目标业务逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例
ForumService proxy = (ForumService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);

// 调用代理实例
proxy.removeForum(10);
proxy.removeTopic(1012);
}
}

运行 TestForumService.main() 输出结果:

1
2
3
4
5
6
7
8
begin monitor...
模拟删除Forum记录:10
end monitor...
com.spring06.ForumServiceImpl.removeForum花费41毫秒
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.spring06.ForumServiceImpl.removeTopic花费21毫秒

运行效果是一致的,横切逻辑代码抽取到了 PerformanceHandler 中。当其他业务类的业务方法需要性能监视时候,只需要为他们创建代理类就行了。

CGLib动态代理

使用 JDK 创建代理有一个限制,即它只能为接口创建代理实例,CGLib 作为一个替代者,填补了这项空缺。

使用 CGLib 之前,需要先导入 CGLib 的 jar 包:

GitHub:https://github.com/cglib/cglib

Maven:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.8</version>
</dependency>

CglibProxy.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
26
27
28
29
30
31
32
package com.spring07;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();

public Object getProxy(Class clazz) {

// 设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 拦截父类所有的方法
// 开始性能监视
PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
// 通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
// 结束性能监视
PerformanceMonitor.end();
return result;
}
}

修改 TestForumService 调用流程:

1
2
3
4
5
6
7
8
9
10
11
package com.spring07;

public class TestForumService {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
// 通过动态生成子类的方式创建代理类
ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
}
}

运行 TestForumService.main() 输出结果:

1
2
3
4
5
6
7
8
begin monitor...
模拟删除Forum记录:10
end monitor...
com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeForum花费60毫秒
begin monitor...
模拟删除Topic记录:1023
end monitor...
com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic花费21毫秒

这里可以看到类名变成了com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic,这个就是 CGLib 动态创建的子类。

由于 CGLib 采用动态创建子类的方式生成代理对象,所以不能对目标类中的 finalprivate 方法进行代理。

基于注解和命名空的AOP编程

Spring 在新版本中对 AOP 功能进行了增强,体现在这么几个方面:

  • 在 XML 配置文件中为 AOP 提供了 aop 命名空间
  • 增加了 AspectJ 切点表达式语言的支持
  • 可以无缝地集成 AspectJ

Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。这种AOP风格的好处在于能够不使用XML来完成功能。

使用引介/引入功能实现为Bean引入新方法

为了更好的理解,这里我新举个例子:

手机接口: Phone

1
2
3
4
5
6
7
8
9
10
package com.spring09;

public interface Phone {

// 打电话
public void call(String str);

// 发短信
public void sendMsg(String str);
}

手机接口实现类: BndPhone

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.spring09;

public class BndPhone implements Phone {
@Override
public void call(String str) {
System.out.println("打电话 - " + str);
}

@Override
public void sendMsg(String str) {
System.out.println("发短信 - " + str);
}
}

手机扩展功能接口: PhoneExtend

1
2
3
4
5
6
7
8
9
10
package com.spring09;

public interface PhoneExtend {

// 听音乐
public void listenMusic(String str);

// 看视频
public void watchVideo(String str);
}

手机扩展功能接口实现类: BndPhoneExtend

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.spring09;

public class BndPhoneExtend implements PhoneExtend {
@Override
public void listenMusic(String str) {
System.out.println("听音乐 - " + str);
}

@Override
public void watchVideo(String str) {
System.out.println("看视频 - " + str);
}
}

注解类: EnablePhoneExtendAspect 这里面定义了切面。这里需要先引入 aspectjrtaspectjweaver 的 jar 包,Maven 的配置代码会在后面贴出。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.spring09;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class EnablePhoneExtendAspect {
@DeclareParents(value = "com.spring09.BndPhone", // 指定手机具体的实现
defaultImpl = BndPhoneExtend.class) // 手机扩展具体的实现
public PhoneExtend phoneExtend; // 要实现的目标接口
}

Spring XML配置:

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

<!-- 开启注解扫描 -->
<context:component-scan base-package="com.spring09"/>

<!-- 开启aop注解方式,默认为false -->
<aop:aspectj-autoproxy/>

<bean id="phone" class="com.spring09.BndPhone"/>
<bean id="phoneExtend" class="com.spring09.BndPhoneExtend"/>
<bean class="com.spring09.EnablePhoneExtendAspect"/>
</beans>

测试类: Test

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

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");

Phone phone = (Phone) ctx.getBean("phone");

// 调用手机原有的方法
phone.call("BNDong");
phone.sendMsg("BNDong");

// 通过引介/引入切面已经将phone实现了PhoneExtend接口,所以可以强制转换
PhoneExtend phoneExtend = (PhoneExtend) phone;
phoneExtend.listenMusic("BNDong");
phoneExtend.watchVideo("BNDong");
}
}

执行 Test.main() 输出结果:

1
2
3
4
打电话 - BNDong
发短信 - BNDong
听音乐 - BNDong
看视频 - BNDong

可以看到 BndPhone 并没有实现 PhoneExtend 接口,但是通过引介/引入切面 BndPhone 拥有了 PhoneExtend 的实现。

我是通过 Maven 构建的 Spring,这里我附上我 Maven 的 pom:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.spring09</groupId>
<artifactId>spring09-demo</artifactId>
<version>1.0-SNAPSHOT</version>

<name>spring09-demo</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<org.springframework.version>4.3.7.RELEASE</org.springframework.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!-- spring start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument-tomcat</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- spring end -->

<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>

<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>

</dependencies>

<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。

在XML中声明切面

基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XML的配置。

Spring 的 AOP 配置元素能够以非侵入性的方式声明切面。

同样使用上面手机的实例代码,我们去掉注解类通过 XML 配置的方式实现切面。

首先删除注解类 EnablePhoneExtendAspect ,这时再运行 Test.main() 就会抛出异常:

1
2
3
4
打电话 - BNDong
发短信 - BNDong
Exception in thread "main" java.lang.ClassCastException: com.spring10.BndPhone cannot be cast to com.spring10.PhoneExtend
at com.spring10.Test.main(Test.java:17)

修改 Spring XML 配置:

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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">

<!-- 开启注解扫描 -->
<context:component-scan base-package="com.spring10"/>

<!-- 开启aop注解方式,默认为false -->
<aop:aspectj-autoproxy/>

<aop:config> <!-- 顶层aop配置元素 -->
<aop:aspect> <!-- 定义一个切面 -->
<aop:declare-parents
types-matching="com.spring10.BndPhone"
implement-interface="com.spring10.PhoneExtend"
delegate-ref="phoneExtend"/>
</aop:aspect>
</aop:config>

<bean id="phone" class="com.spring10.BndPhone"/>
<bean id="phoneExtend" class="com.spring10.BndPhoneExtend"/>
</beans>
  • <aop:config> :顶层aop配置元素。

  • <aop:aspect> :定义一个切面。

  • <aop:declare-parents>:声明切面所通知的bean要在它的对象层次结构中拥有新的父类型。

  • types-matching :类型匹配的接口实现。

    • implement-interface :要实现的接口。
  • delegate-ref :引用一个Spring bean作为引入的委托。

    • default-impl :用全限定类名来显示指定实现。

这时运行 Test.main() 发现切面配置成功:

1
2
3
4
打电话 - BNDong
发短信 - BNDong
听音乐 - BNDong
看视频 - BNDong

切面类型总结图:

总结

  • Spring 采用 JDK 动态代理和 CGLib 动态代理技术在运行期织入增强。
  • JDK 动态代理需要目标类实现接口,而 CGLib 不对目标类作任何限制。
  • JDK 在创建代理对象时的性能高于 CGLib,而生成的代理对象的运行性能却比 CGLib 的低。
  • Spring 只能在方法级别上织入增强。
  • Spring 的 AOP 配置元素能够以非侵入性的方式声明。
  • 当 Spring AOP 不能满足需求时,我们必须转向更为强大的 AspectJ。

参考资料

《Spring实战(第4版)》Craig Walls 著 / 张卫滨 译 下载(密码:8ts2)

《精通Spring 4.x 企业应用开发实战》陈雄华 林开雄 文建国 编著 下载(密码:my25)

https://baike.baidu.com/item/AOP/1332219?fr=aladdin

https://juejin.im/post/5b06bf2df265da0de2574ee1

https://segmentfault.com/a/1190000007469968

↓赏一个鸡腿... 要不,半个也行↓