Spring AOP(下)原理
本文我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。Spring AOP 是基于动态代理来实现 AOP 的;
1. 代理模式
1.1 代理弄模式的定义
代理模式,也叫委托模式。
定义:为其他对象提供一种代理以控制这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。
某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。
使用代理前:
使用代理后:
就像生活中的例子,生活中的代理类似与艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。
1.2 代理模式的主要角色:
1、Subject:业务接口类。可以是抽象类或者接口(不一定有)。
2、RealSubject:业务实现类。具体的业务执行,也就是被代理对象。
3、Proxy:代理类。RealSubject的代理。
比如让xox代言:
Subject:小偶像就是提前定义了小偶像要和合作方做的事情,交给经纪人代理处理;
RealSubject:小偶像。
Proxy:经纪人。
UML类图如下:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理和动态代理。
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
2. 静态代理
静态代理:在程序运行前,代理类的 .class文件就已经存在了。(签合约之前,小偶像已经将自己能做到的义务写到了文案里面,就等合约方签字)。
我们通过代码来加深理解。以经纪人为例:
2.1 定义接口
(定义小偶像要接的剧和能参加的活动,也是经纪人需要做的事情):
public interface Xox {
void receiveJuben();
//xox要接受剧本
}
2.2 实现接口
(沈梦瑶要接剧本):
public class ShenMengyao implements Xox{
@Override
public void receiveJuben() {
System.out.println("我是沈梦瑶,我要接剧本");
}
}
2.3 代理
(经纪人,帮沈梦瑶接剧本):
public class XoxProxy implements Xox{
private Xox target;
public XoxProxy(Xox target){
this.target = target;
}
@Override
public void receiveJuben() {
//代理前
System.out.println("我是经纪人, 开始代理");
//出租房子
target.receiveJuben();
//代理后
System.out.println("我是经纪人, 结束代理");
}
}
2.4 使用
package com.example.aop;
public class Main {
public static void main(String[] args) {
Xox xox = new ShenMengyao();
//创建代理类
XoxProxy xoxProxy = new XoxProxy(xox);
//通过代理类访问⽬标⽅法
xoxProxy.receiveJuben();
}
}
运行结果:
上面这个代理实现方式就是静态代理(仿佛啥也没干)。从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活。所以日常开发几乎看不到静态代理的场景。
接下来新增需求:经纪人又新增了其他业务:代理小偶像解约。我们就需要对上述代码进行修改。
2.5 更新代理内容
1、接口定义修改:
package com.example.aop;
public interface Xox {
void receiveJuben();
//xox要接受剧本
void Termination();
//小偶像要解约
}
2、接口实现修改:
package com.example.aop;
public class ShenMengyao implements Xox{
@Override
public void receiveJuben() {
System.out.println("我是沈梦瑶,我要接剧本");
}
@Override
public void Termination() {
System.out.println("我是沈梦瑶,我要和丝芭解约");
}
}
3、代理类修改:
package com.example.aop;
public class XoxProxy implements Xox{
private Xox target;
public XoxProxy(Xox target){
this.target = target;
}
@Override
public void receiveJuben() {
//代理前
System.out.println("我是经纪人, 开始代理");
//出租房子
target.receiveJuben();
//代理后
System.out.println("我是经纪人, 结束代理");
}
@Override
public void Termination() {
//代理前
System.out.println("我是经纪人, 开始代理");
//出租房子
target.Termination();
//代理后
System.out.println("我是经纪人, 结束代理");
}
}
4、使用:
package com.example.aop;
public class Main {
public static void main(String[] args) {
Xox xox = new ShenMengyao();
//创建代理类
XoxProxy xoxProxy = new XoxProxy(xox);
//通过代理类访问⽬标⽅法
xoxProxy.receiveJuben();
System.out.println("-----------");
xoxProxy.Termination();
}
}
从上述代码可以看出,我们修改xox接口,接需要在后续修改三个代码,这样十分加大了我们的 工作量,即每增加一个接口就需要实现类来完善该接口的方法,我们的代理也需要进行完善相应的代码,为了减少工作量,所以接下来学习动态代理技术;
3. 动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时,由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成。
比如经纪人,她不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。
先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:
1、JDK动态代理
2、CGLIB动态代理
动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
3.1 JDK动态代理
3.1.1 JDK动态代理实现步骤
1、定义一个接口及其实现类(静态代理中的 Xox和 shenmengyao)。
2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑。
3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ] Interfaces, InvocationHandler h)方法创建代理对象。
3.1.2 定义JDK动态代理类
创建 JDKInvocationHandler类 实现 InvocationHandler 接口:
package com.example.aop;
import org.springframework.cglib.proxy.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
//目标对象,即被代理的对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理增强内容
System.out.println("我是经纪人,开始代理");
//通过反射调用被代理类的方法
Object result = method.invoke(target, args);
//代理增强内容
System.out.println("我是经纪人,结束代理");
return result;
}
}
创建一个代理对象并使用:
package com.example.aop;
import org.springframework.cglib.proxy.Proxy;
public class Main {
public static void main(String[] args) {
/**
* JDK动态代理
*/
//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
ShenMengyao shenMengyao = new ShenMengyao();//目标对象
/**
* newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* loader:加载我们的被代理类的ClassLoad
* interfaces:要实现的接口
* h:代理要做的事情,需要实现 InvocationHandler 这个接口
*/
Xox proxy = (Xox) Proxy.newProxyInstance(
shenMengyao.getClass().getClassLoader(),
new Class[]{Xox.class},
new JDKInvocationHandler(shenMengyao)
);
//通过代理类访问⽬标⽅法
proxy.receiveJuben();
System.out.println("-----------");
proxy.Termination();
}
}
运行程序,结果如下:
假设代理的是类,而不是对象,代码如下:
package com.example.aop;
import org.springframework.cglib.proxy.Proxy;
public class Main {
public static void main(String[] args) {
/**
* JDK动态代理
*/
ShenMengyao shenMengyao = new ShenMengyao();
ShenMengyao proxy = (ShenMengyao) Proxy.newProxyInstance(
shenMengyao.getClass().getClassLoader(),
new Class[]{ShenMengyao.class},
new JDKInvocationHandler(shenMengyao)
);
//通过代理类访问⽬标⽅法
proxy.receiveJuben();
System.out.println("-----------");
proxy.Termination();
}
}
运行程序,结果如下:(报错了)
3.1.3 代码简单讲解
主要是学习API的使用,我们按照 Java API 的规范来使用即可。
1、InvocationHandler:
InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。
public interface InvocationHandler {
/**
* 参数说明
* proxy:被代理对象
* method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
* args:method所对应⽅法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。
2、Proxy:
Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//...代码省略
}
这个方法一共有 3 个参数:
loader:类加载器,用于加载被代理对象。
interface:被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。
h:代理要做的事情,实现 InvocationHandler 接口的对象。
3.2 CGLIB动态代理
JDK动态代理有一个最致命的问题,是只能代理实现了接口的类。
有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成。
CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的)
3.2.1 CGLIB 动态代理类实现步骤
1、定义一个类(被代理类)。
2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似。
3、通过 Enhancer 类的 create() 创建代理类。
接下来看实现:
3.2.2 添加依赖
和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
3.2.3 自定义MethodInterceptor(方法拦截器)
实现 MethodInterceptor 接口:
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor {
private Object target;
public CGLibInterceptor(Object target) {
this.target = target;
}
/**
* 调用代理对象的方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
//代理增强内容
System.out.println("我是中介,开始代理");
Object result = method.invoke(target, args);
//代理增强内容
System.out.println("我是中介,结束代理");
return result;
}
}
3.2.4 创建代理类,并使用
代理接口:
public class Main {
public static void main(String[] args) {
//目标对象
Xox target = new ShenMengyao();
Xox proxy = (Xox) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
proxy.receiveJuben();
System.out.println("=============");
proxy.
Termination();
}
}
运行程序,执行结果如下:
代理类:
public class Main {
public static void main(String[] args) {
//目标对象
Xox target = new ShenMengyao();
ShenMengyao proxy = (ShenMengyao) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
proxy.receiveJuben();
System.out.println("=============");
proxy.Termination();
}
}
结果没有发生变化;
3.2.5 代码简单讲解
1、MethodInterceptor:
MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。
public interface MethodInterceptor extends Callback {
/**
* 参数说明:
* o: 被代理的对象
* method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
* objects: ⽅法⼊参
* methodProxy: ⽤于调⽤原始⽅法
*/
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
2、Enhancer.create():
public static Object create(Class type, Callback callback) {
//...代码省略
}
type:被代理类的类型(类或接口)
callback:自定义方法拦截器 MethodInterceptor
4. 常见面试题
1、什么是 AOP?
AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。
2、Spring AOP的实现方式有哪些?
(1)基于注解(@Aspect 或 自定义注解)(2)基于 xml
(3)基于代理
3、Spring AOP 的实现原理?
基于动态代理实现的,其中的动态代理有两种形式:(1)JDK (2)CGLIB
4、Spring 使用的是哪种代理方式?
Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。
Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理;
5、JDK 和 CGLIB 的区别?
使用JDK 动态代理只能代理接口。
使用 CGLIB 动态代理 既可以代理接口,也可以代理类。
6、总结
1、AOP 是一种思想,是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现。(2)基于自定义注解来实现,还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见。
3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现。(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关。
ps:本文就写到这里了,谢谢观看!!!