第十二章 Spring之不得不了解的内容——AOP概念篇
Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
文章目录
- Spring源码阅读目录
- 第一部分——IOC篇
- 第二部分——AOP篇
- 前言
- 尝试动手写IOC容器
- 啥?代理,连接点,切点。。。脑壳嗡嗡的
- 一、Joinpoint(连接点)—— 哪些地方可以增强?
- 二、Pointcut(切点)—— 如何找到这些地方?
- 三、Advice(通知)—— 找到后要怎么增强?
- 四、Aspect(切面)—— 多个增强如何管理?
- 五、Weaver(织入器)—— 如何对其进行增强?
- JDK代理
- CGLIB代理
- 六、Target Object(目标对象)
- 总结
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第十一章 Spring之不太熟的熟人——AOP 中,A君 已经大致了解了 AOP 的用途。接下来看看 A君 又会面临什么样的挑战吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大要求A君在一周内开发个简单的 IOC容器
前情提要: 老大 要求 A君 先去了解下 AOP 的相关知识,后续准备相关内容的开发 。。。
啥?代理,连接点,切点。。。脑壳嗡嗡的
想要自己整个 AOP 单单了解其用途是远远不够的。这不,A君 又去了解 AOP 实现的一些具体细节。不看不要紧,什么 连接点、切点、切面、通知。。。 这还怎么玩?吓得 A君 差点当场离职,连夜跑路
“冷静、冷静” A君 心里默念,深吸了一口气,A君 借鉴之前的学习经验,不再去尝试直接理解这些概念,而是从开发的角度,扪心自问:“如果让我来写,我会怎么做?” A君 略微思索了下,简单列出几步:
- 哪些地方可以增强?
- 如何找到这些地方?
- 找到后要怎么增强?
- 多个增强如何管理?
- 如何对其进行增强?
提出问题后,接下来就是如何解决了,对此 A君 给出自己的答复:
- 方法可以增强
- 通过方法名参数、正则等进行匹配
- 方法调用前、方法调用后、方法调用前后、出现异常时候可以增强
- 可以定义一个管理者,用来管理增强信息
- 继承目标类,重写方法,对其增强
有了思路后, A君 再去看那么定义,瞬间觉得眉清目秀了。接下来就是把概念和自己的想法相互印证,看看自己有什么不足的地方。由于 A君 主要抄袭 Spring (o゚▽゚)o ,所以下面的概念主要以 Spring 为主。当然,如果追求更具体的规范,可以参考 AOP联盟
一、Joinpoint(连接点)—— 哪些地方可以增强?
Joinpoint(连接点) 指程序执行中的一个点,AOP 可以在这个点上应用某种增强或切面逻辑。它定义了在程序运行时的哪些时刻可以插入切面行为
虽然概念依旧拗口, A君 却心如明镜,Joinpoint(连接点) 就是定义 AOP 对目标对象可以增强的地方。Spring 中只支持方法级别,当然,这是 Spring 的定义,只要你想,不止方法,还有:属性、构造器、异常等。更细致的也可以,比如循环这些也成。不过凡事讲究一个性价比,买东西如此,写代码同理。我们希望的是用20%的投入,获得80%的收益。而不是80%投入,20%的收益。简而言之,可以,但是没必要
二、Pointcut(切点)—— 如何找到这些地方?
Pointcut(切点) 指用于定义一组 Joinpoints 的表达式,它决定了在程序执行的哪些特定点上应用增强逻辑
Pointcut(切点) 应该是最好理解的一个概念了,例如 A君 想增强 A.a(),自然而然就会想到要怎么去匹配到 A.a() 这个方法了,用方法名也好,正则也好,亦或是表达式,这些都是具体实现的问题了,而它们统一的名字叫做 Pointcut(切点) 。当规定好哪些地方可以可以作为 Joinpoint(连接点) 后,Pointcut(切点) 就是用来匹配到对应的 Joinpoint(连接点)
三、Advice(通知)—— 找到后要怎么增强?
Advice(通知) 定义了在方法执行前、执行后、或者在方法抛出异常时应该采取的操作。它是连接横切关注点(如事务管理、日志记录、权限检查等)与程序逻辑的一种机制
Advice(通知) 也比较好理解,依旧以增强 A.a() 为例,你希望在哪里增强?方法调用前,还是方法调用后,还是出现异常的时候?当然这些都是俗语,在 AOP 中,它们有自己的名字:
-
Before Advice(前置通知):方法调用前触发
-
After Advice(后置通知):这个就有意思了,后置通知,方法出现异常执行吗?还是只有在方法返回后执行。所以 After Advice(后置通知) 还有更细致的区分
- After Returning Advice:在方法正常返回后触发
- After Throwing Advice:在方法抛出异常后触发
- After (Finally) Advice:无论方法是否正常返回或抛出异常,都会在方法结束后触发
-
Around Advice(环绕通知):顾名思义,把方法环绕起来,包围目标方法的调用,也就是前置+后置
-
Introduction(引介):这个增强最为特殊,它不是根据方法执行时机来进行划分的。而是以类为维度,赋予类新功能,如:实现新的接口
四、Aspect(切面)—— 多个增强如何管理?
Aspect(切面) 是一段横切逻辑的模块化封装,它封装了横切关注点(cross-cutting concerns),并将这些逻辑应用到不同的目标对象或方法上
Aspect(切面) 可以理解成 Advice(通知) 和 Pointcut(切点) 的管理者,显然,一个 Advice(通知) 可以对于多个 Pointcut(切点) ,一个 Pointcut(切点) 也可以对于多个 Advice(通知) ,二者是多对多的关系。不过在 Spring 中有它自己的设计,这个后边也会慢慢接触到
五、Weaver(织入器)—— 如何对其进行增强?
Weaver(织入器) 用于将 Aspect(切面) 应用到目标对象或类上。在 AOP 中,Weaver(织入器) 负责将横切 Advice(通知) 动态或静态地织入到目标类的指定 Joinpoint(连接点) 上
经过上面一系列的铺垫,接下来只要用 Pointcut(切点) 匹配到对应的 Joinpoint(连接点) ,将对应的 Advice(通知) 织入即可,这也就是 Weaver(织入器) 要干的活,在 Spring 中的具体实现是 ProxyFactory
说到 ProxyFactory 就不得不了解一下 代理模式 了,对于 代理模式,A君 背面试题的时候,都快看吐了,什么 静态代理、动态代理 云云。为了实现 AOP,A君 只能硬着头皮看下去,这一切还得从 代理模式 说起。。。
代理模式 主要是用于做一些访问权限的控制,避免目标类直接暴露出来。A君 写了个简单的例子,首先定义一个目标类,如下:
public class A {
public void invoke() {
System.out.println("A君正在搞飞机。。。");
}
}
再定义一个代理类,如下:
public class ProxyModel extends A {
private A a = new A();
@Override
public void invoke() {
System.out.println("进行安全检查#######");
super.invoke();
}
}
最终测试结果如下:
这个就是最简单的 代理模式 ,也可以称作 静态代理。静态代理 虽然好,但像安全检查这类功能,一般来说,大部分类都需要涉及到。这样子每个类都要定义一个代理类,人都要疯掉了,这时候 动态代理 就应运而生了
动态代理 正如它的名字一样,重点在于动态,那问题来了,要怎么动态呢?主要分为:编译期、运行期。编译期代理就是在编译的时候就生成代理类,而运行期则是在程序运行时候创建对应的代理(额,好像是废话)。编译期代理和 A君 这次要做的没多大关联,所以这边不进行阐述,这里 A君 主要了解的是运行期代理,即是大家都耳熟能详的 JDK代理 和 CGLIB代理
JDK代理
JDK代理 是 JDK1.3 之后引入的,这个代理可以根据指定接口,在运行时动态生成一个代理对象。A君 依旧写了个简单的例子,先定义一个简单接口,如下:
public interface IA {
void invoke();
}
实现类如下:
public class A1 implements IA {
@Override
public void invoke() {
System.out.println("A君在jdk代理搞飞机。。。。");
}
}
接下来就是关键的部分了,需要实现 InvocationHandler接口 对目标类进行增强,如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class AInvocationHandler implements InvocationHandler {
private IA target;
public AInvocationHandler(IA ia) {
this.target = ia;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进行jdk安全检查##########");
return method.invoke(target, args);
}
}
测试也简单,如下:
JDK代理 设想很美好,也是 Spring 首选的代理方式(如果可以的话),但是 JDK代理 也有其不足,那就是很多类不一定都有实现接口,这时候 JDK代理 就鞭长莫及了
CGLIB代理
CGLIB代理 是基于继承实现的代理,依赖于动态字节码技术,通过覆写父类的方法来进行拓展。A君 也写了个例子,因为 CGLIB代理 依赖于第三方jar包,所以先导包,如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version> <!-- 或其他你需要的版本 -->
</dependency>
在定义一个简单的目标类,如下:
public class A2 {
public void invoke() {
System.out.println("A君在cglib代理搞飞机。。。。");
}
}
在添加个增强类,需要实现 MethodInterceptor接口,如下:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class AInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("进行cglib安全检查 ##########");
return proxy.invokeSuper(obj, args);
}
}
测试结果,如下:
六、Target Object(目标对象)
Target Object(目标对象) 指的是被 AOP 增强(或者说,被织入横切关注点)的原始对象
Target Object(目标对象) 这个更不用说,被增强的目标对象。比如:要增强 A.a() ,那么 A 就是 Target Object(目标对象)
至此,A君 已经大致了解了整个 AOP 工作流程,可以开始着手具体的开发了。对此,A君 还是喜不自胜的,能把不懂的东西弄懂,不也是一种乐趣吗?
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)