代理动态代理
代理
1.这是什么?
- 假设你想租一套房子。房东提供了房子,但你并不直接与房东接触,而是通过房屋中介来进行。房屋中介在这个过程中充当了一个代理的角色。
- 银行自动提款机。ATM 机代表银行进行一些实际操作,而不直接暴露银行的核心系统。ATM 机通过代理模式,控制了访问银行核心交易系统的权限,确保了账户信息的安全,并且提供了客户可以操作的界面。
- 网络代理(如VPN)是现代互联网使用中常见的代理模式应用,尤其在保护用户隐私和绕过网络限制方面。
- …
这些例子都展示了代理模式如何在不同的场景中提供了对“真实对象”的间接控制。通过上面这些例子,可以看到“代理”大致有三个角色:客户端(使用者)、代理(中间层)、真实对象(交付委托的对象、目标对象)。
为什么要使用代理呢?
- 控制访问权限、增加安全性: 代理模式可以控制客户端对真实对象的访问,尤其在需要做权限控制时非常有用。例如atm机,仅提供几个有限的功能给用户使用,其他的功能用户想用也用不了。
- 逻辑增强: 代理对象可以在真实对象的基础之上,增加额外的逻辑。例子:租房中介(房东发布房源,中介可以在房源信息的基础之上增加看房、筛选、签合同、确认合同条款等等逻辑,房东就不用操心这些)。在编程代码中,代理模式能够将客户端和真实对象解耦。客户端通过代理访问真实对象,而不需要知道实际对象的细节。这种解耦有助于系统的维护和扩展。
- …
通过使用代理模式,系统能够在不直接暴露真实对象的情况下,提供更多的控制和优化,这对于实际的软件设计和架构非常重要。
2.静态代理
静态代理是代理模式的一种实现方式,其中代理类在编译时就已经确定,代理类和真实类都会实现相同的接口(或者继承相同的类),通过代理类来控制对真实对象的访问。静态代理的“静态”指的是代理类是在编译时就已经创建好了,代理类的行为是固定的。
静态代理的结构:
- Subject(主题接口):定义了真实对象和代理类共同实现的接口,通常包含一个或多个方法。
- RealSubject(真实主题):实现了
Subject
接口,是真正需要被代理的对象。 - Proxy(代理类):实现了
Subject
接口,内部持有对RealSubject
对象的引用,通过该引用来控制对真实对象的访问,并可以在调用之前或之后加入额外的操作(如日志记录、权限验证等)。 - User(也就是我们所说的客户端,就是调用的)
Subject (出租房子)
// Subject接口,定义了真实对象和代理类要实现的行为
public interface Subject {
void request();
}
RealObject (房东)
// RealSubject实现了Subject接口,是真正的业务实现类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
Proxy (中介)
// Proxy类也实现了Subject接口,并在请求前后添加额外的操作
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
this.realSubject = new RealSubject(); // 创建真实对象
}
@Override
public void request() {
// 在调用真实对象的方法之前,可以加入一些额外的操作
System.out.println("Proxy: Before");
// 调用真实对象的方法
realSubject.request();
// 在调用真实对象的方法之后,也可以添加额外的操作
System.out.println("Proxy: After");
}
}
User (使用者)
public class Client {
public static void main(String[] args) {
// 客户端使用Proxy类来访问请求
Subject subject = new Proxy(); // 使用代理类
subject.request(); // 调用代理方法
}
}
可以看到其实现特别简单,代理对象持有一个真是对象的实例,然后调用真实对象的方法就可以了。如果,真实对象想要新增一个功能呢?那么,Subject 、RealObject 、Proxy 都会动代码
优点:
- 实现简单
缺点
- 代理类与真实类具有相同的接口,每增加一个代理类就需要创建一个相应的代理类,增加了代码的重复性和维护的难度。
可以看到,静态代理适用于简单的系统,当代理逻辑固定、代理类数量不多时,静态代理是一个简单且有效的方案。
3.动态代理
①初步
动态代理是代理模式的另一种实现方式,它与静态代理不同,代理类是在运行时动态生成的,而不需要在编译时就确定。
说到运行时,这不马上就想到反射了吗?
我们首先来运用反射,来自定义一个动态代理。
思想: 客户端只需要选择调用什么方法,传哪些参数就可以了
// 功能
public interface Subject {
String takeMoney(String uid); // 取钱的功能
// void putMoney(Integer num); // 存钱的功能
}
银行
// 银行
public class Bank implements Subject{
@Override
public String takeMoney(String uid) {
return uid + " 取出 200 元";
}
/*
@Override
public void putMoney(Integer num) {
System.out.println("存钱: " + num + "元");
}
*/
}
atm机
import java.lang.reflect.Method;
// ATM机
public class ATM {
private Class<Bank> typeClass;
private Bank bank;
public ATM() {}
public ATM(Class<Bank> typeClass) {
this.typeClass = typeClass;
}
private Object invoke(String methodName, Object... args) {
Class<?>[] paramsType = new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramsType[i] = args[i].getClass();
}
// 功能名, 参数列表
if ( typeClass != null ){
// 创建一个银行对象
try {
if ( this.bank == null ) this.bank = typeClass.newInstance(); // 如果是空的,创建一个实例
Method method = typeClass.getMethod(methodName, paramsType);
return method.invoke(this.bank, args);
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR";
} else {
System.out.println("【ATM机没弄好】~~~~~~");
return "ERROR";
}
}
public Object atmProcess(String methodName, Object... args) {
System.out.println("【尊贵的客户您好,atm为您服务】");
return invoke(methodName, args);
}
}
使用者
// 使用者
public class Client {
public static void main(String[] args) {
ATM atm = new ATM(Bank.class);
Object takeResult = atm.atmProcess("takeMoney", "uid123456");
System.out.println(takeResult);
/*
Object o = atm.atmProcess("putMoney", 600); // 结果是null,因为返回值void
System.out.println(o);
*/
}
}
如果这个时候,功能需要新增一个,那就是存钱,putMoney( Integet num);我们只需要改bank和subject就可以了(上面代码中注释掉的地方),我们的ATM完全不需要改。
诚然,这样实现还有非常多的问题,但是,我们可以大致看到这样的:利用 Java 反射机制,我们可以通过方法名和参数类型来动态调用对象的方法。这种方式使得我们能够在运行时操作对象的方法,而不需要事先知道具体的实现类或方法细节。
动态代理的优缺点总结:
优点:
- 减少代码重复
- 运行时灵活性
- 通用性
缺点:
- 性能开销:动态代理需要使用反射机制来创建代理类和调用方法,相比静态代理会有性能损失。
②jdk动态代理
JDK 动态代理是一种 运行时代理 技术,它允许你在程序运行时创建代理对象,而不需要提前编写代理类。与传统的静态代理不同,JDK 动态代理是通过反射机制在运行时生成代理类,并且代理类和真实类实现相同的接口。
看好了啊,是==【运行时生成代理类】==,是生成的代理类。与上个例子是不一样的。
他的核心思想:通过 Proxy.newProxyInstance
方法创建一个代理对象,该代理对象会拦截对目标对象方法的调用,然后通过 InvocationHandler
接口的 invoke
方法处理具体的业务逻辑。
1)JDK 动态代理的基本原理
- 目标接口(Subject):被代理的类必须实现一个接口。JDK 动态代理 只支持接口代理,如果类没有实现接口,JDK 动态代理就无法使用。这个接口通常定义了业务方法。
InvocationHandler
接口:该接口定义了一个方法invoke(Object proxy, Method method, Object[] args)
,它会在每次调用代理对象的方法时被执行。你可以在这个方法内执行一些额外的逻辑【逻辑增强】,如日志记录、权限检查、事务处理等。Proxy.newProxyInstance
:这个方法是 JDK 动态代理的关键,通过它来创建代理对象。它接收三个参数:- 类加载器:代理对象的类加载器,通常传入目标对象的类加载器。
- 接口列表:代理类实现的接口。可以是多个接口。
InvocationHandler
实现:代理对象的行为处理器,定义了具体的操作。
我们还是拿银行atm机来举例子
- 定义接口: 操作接口
public interface Subject {
String takeMoney(String uid); // 取钱的功能
void putMoney(Integer num); // 存钱的功能
}
- 实现真实对象:Bank
public class Bank implements Subject {
@Override
public String takeMoney(String uid) {
System.out.println("【银行---用户】: " + uid);
return "【银行】用户" + uid + "取走" + 600 + "元!";
}
@Override
public void putMoney(Integer num) {
System.out.println("【银行---存入】: " + num + " 元");
}
}
- 创建
InvocationHandler
:定义代理行为( ATM做的事儿 )
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ATM implements InvocationHandler {
private Object realSubject;
public ATM(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标方法前记录日志
System.out.println("【proxy】运行前: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(realSubject, args);
// 在调用目标方法后记录日志
System.out.println("【proxy】运行后: " + method.getName());
return result;
}
}
- 创建代理对象、用户使用:通过
Proxy.newProxyInstance
创建代理
import java.lang.reflect.Proxy;
public class ATMClient {
public static void main(String[] args) {
// 创建真实对象 【realObject】
Bank realBank = new Bank();
// 创建 InvocationHandler 【代理逻辑】
ATM atm = new ATM(realBank);
// 创建代理对象 【proxy】
Subject proxyBank = (Subject) Proxy.newProxyInstance(
realBank.getClass().getClassLoader(),// 类加载器
realBank.getClass().getInterfaces(),// 实现的接口
atm// InvocationHandler
);
// 使用代理对象执行方法 【use】
proxyBank.takeMoney("userTXF"); // 调用代理的 takeMoney 方法
System.out.println("=====================");
proxyBank.putMoney(1000); // 调用代理的 putMoney 方法
System.out.println(proxyBank.getClass());
}
}
会发现,很奇怪???!!这是啥,我这个代码里面没有写过啊??!
都说了,是==【运行时生成代理类】==。代理类实例在程序运行时,由JVM根据反射机制动态的生成。也就是说代理类不是用户自己定义的,而是由JVM生成的。然后发现打包的target目录里面也没有。JVM默认不创建该.class文件,需要增加一个启动参数:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true。
可以在client类的main第一行加这个。System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
,然后运行,就默认在项目的根目录下生成一个com目录,然后就可以发现生成的class文件了。
为啥加了这个参数就可以出现这个文件了呢? 我们分析一下Proxy的一些源码可知其端倪。
// Proxy.java
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
// 他的静态内部类
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
private static final String proxyClassNamePrefix = "$Proxy"; // 解释了为什么生成的代理类名字是这个了
....
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
......
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement(); // 后面的数字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//Generate the specified proxy class. 可以看到generateProxyClass有这个方法,名字很形象“生成代理类class”
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
....
}
}
}
//ProxyGenerator.class
public class ProxyGenerator {
......
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
......
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
.......
if (saveGeneratedFiles) { // 就是根据这个变量来决定的
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
.....
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
...........
}
}
2)Proxy
生成的**$Proxy0.class(我这里是0)**是什么样子的?通过上一节生成的来查看一下
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0; // 代理类代理的五个方法,为什么是五个?因为把equals,toString,hashCode也代理了
public $Proxy0(InvocationHandler var1) throws {super(var1);}
public final boolean equals(Object var1) throws {
...
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
...
}
public final void putMoney(Integer var1) throws {
...
super.h.invoke(this, m4, new Object[]{var1});
...
}
public final String takeMoney(String var1) throws {
...
return (String)super.h.invoke(this, m3, new Object[]{var1});
...
}
public final String toString() throws {
...
return (String)super.h.invoke(this, m2, (Object[])null);
...
}
public final int hashCode() throws {
...
return (Integer)super.h.invoke(this, m0, (Object[])null);
...
}
// 静态代码块,最先执行
static {
...
// 利用反射获取方法字段
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.feng.proxy.jdk.Subject").getMethod("putMoney", Class.forName("java.lang.Integer"));
m3 = Class.forName("com.feng.proxy.jdk.Subject").getMethod("takeMoney", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
...
}
}
可以看到生成的这个代理类,里面五个方法的具体实现,都是super.h.invoke(xxx); 我们继续跟进去看一下
public class Proxy implements java.io.Serializable {
...
protected InvocationHandler h; //持有实例,调用的是实现了InvocationHandler的类的invoke方法
...
// 他的静态方法
public static Object newProxyInstance(ClassLoader loader, //代理对象的类加载器
Class<?>[] interfaces, //代理类实现的接口。可以是多个接口。
InvocationHandler h) // 自定义实现InvocationHandler的类对象
throws IllegalArgumentException
{
Objects.requireNonNull(h);
....
Class<?> cl = getProxyClass0(loader, intfs);
try {
....
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
...
return cons.newInstance(new Object[]{h}); // 返回--这里就没有深究了。。。
} ...
}
}
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这也就解释了“为什么jdk动态代理的类必须要实现接口”?
发现 Proxy这个类只是保存了动态代理的处理器 InvocationHandler,InvocationHandler是接口呀。。。
/*
原因是JDK动态代理是基于接口实现的。
当你使用Proxy类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型。
代理对象的方法调用会被转发到实现InvocationHandler接口的类中的invoke()方法。这个invoke()方法接受三个参数:代理对象本身、被调用的方法对象和方法的参数数组。invoke()方法需要返回被代理方法调用的结果。
由于代理对象的类型是由接口列表决定的,因此只有实现了接口的类才能被代理。如果你想代理一个类而不是一个接口,你需要使用其他的代理技术,比如CGLIB。
原文链接:https://blog.csdn.net/weixin_58093525/article/details/131737232
引用一下 B站Mic 【Java面试】JDK动态代理为什么只能代理有接口的类?【https://www.bilibili.com/video/BV19XtPecE6A/?spm_id_from=333.337.search-card.all.click&vd_source=9ae5b46de4ed16e5ec825e9e94655531】
1.动态代理本身的使用场景或者需求,只是对原始实现的一个拦截,然后去做一些功能的增强或者扩展。而实际的开发模式也都是基于面向接口来开发,所以基于接口来实现动态代理,从需求和场景都是吻合的。当然确实可能存在有些类没有实现接口的,那这个时候,JDK动态代理确实无法满足。
2.在 Java里面,类的继承关系的设计,更多的是考虑到共性能力的抽象,从而提高代码的重用性和扩展性,而动态代理也是在做这样一个事情,它封装了动态代理类生成的抽象逻辑、判断一个类是否是动态代理类、InvocationHandler的持有等等,那么把这些抽象的公共逻辑放在 Proxy这个分类里面,很显然是一个比较正常的设计思路。
总的来说,我认为这个设计上并没有什么特别值得讨论的地方,因为我认为技术方案的设计是解决特定场景问题的。
如果一定要针对普通类来做动态代理,可以选择 cglib这个组件,它会动态生成一个被代理类的子类,子类重写了父类中所有非 final修饰的方法,在子类中拦截父类的所有方法调用从而实现动态代理。
*/
3)思考
如果RealObject【Bank类】是下面的样子呢?然后用生成的代理类去运行,会是怎样的呢
public class Bank implements Subject {
@Override
public String takeMoney(String uid) {
System.out.println("【银行---用户】: " + uid);
putMoney(6666); // 方法之间的调用==========================
return "【银行】用户" + uid + "取走" + 600 + "元!";
}
@Override
public void putMoney(Integer num) {
System.out.println("【银行---存入】: " + num + " 元");
}
}
客户端测试代码
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 创建真实对象
Bank realBank = new Bank();
// 创建 InvocationHandler
ATM atm = new ATM(realBank);
// 创建代理对象
Subject proxyBank = (Subject) Proxy.newProxyInstance(
realBank.getClass().getClassLoader(), // 类加载器
realBank.getClass().getInterfaces(), // 实现的接口
atm // InvocationHandler
);
// 使用代理对象执行方法
proxyBank.takeMoney("user");
}
/* 运行结果---
【proxy】运行前: takeMoney
【银行---用户】: user
【银行---存入】: 6666 元
【proxy】运行后: takeMoney
*/
会发现,内部的putMoney并没有执行代理增强的逻辑。只有最外层的takeMoney执行了增强逻辑。其实分析其调用过程就非常清晰了。
// 第一步 ---- 生成的$Proxy0代理类中
public final String takeMoney(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1}); // 然后调用实现类的invoke方法(到第二步)
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 第二步 ---- 自定义的InvocationHandler实现类
public class ATM implements InvocationHandler {
....
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【proxy】运行前: " + method.getName()); // 1. 输出:【proxy】运行前: takeMoney
Object result = method.invoke(realSubject, args); // 执行真实对象的方法(去往第三步)
System.out.println("【proxy】运行后: " + method.getName()); // 4. 输出:【proxy】运行后: takeMoney
return result;
}
}
// 第三步 ---- 真实对象Bank
public class Bank implements Subject {
@Override
public String takeMoney(String uid) {
System.out.println("【银行---用户】: " + uid); // 2. 输出:【银行---用户】: user
putMoney(6666); // 调用方法,这里仅仅是真是对象Bank调用的自己的另一个方法!!
return "【银行】用户" + uid + "取走" + 600 + "元!";
}
@Override
public void putMoney(Integer num) {
System.out.println("【银行---存入】: " + num + " 元"); // 3. 输出: 【银行---存入】: 6666 元
}
}
嵌套调用的话,内部只是简单的方法调用,并不是执行的代理类里面的方法。
③cglib动态代理
由第②节引出一个问题,如果真实对象没有实现接口怎么办?那就是用cglib动态代理,可以说是对jdk动态代理的有效补充。
使用cglib动态代理需要引入第三方包.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
cglib原理:动态生成一个真实类的子类,子类重写真实类的所有不是 final 的方法。在子类拦截所有父类方法的调用,织入增强逻辑。
织入??好熟悉的字眼?Spring? Aop? 啊啊啊,头好痛。。。。
cglib底层采用的是ASM字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。
1)cglib的使用
通过继承目标类,并重写目标类的所有方法,从而在方法调用时加入额外的逻辑(如日志、事务管理、权限检查等)。CGLIB 通过字节码技术实现动态生成目标类的子类,并且利用 MethodInterceptor 来拦截方法调用。
- 仍然是银行的例子
public class Bank { //定义了存款和取款的方法,实现了实际的业务逻辑
private int money;
public void putMoney( int count ) {
System.out.println("【银行】: 存入 " + count + "元");
money += count;
}
public void takMoney( int count ) {
if ( count > money ) System.out.println("【银行】: 取钱失败,余额不足");
else {
System.out.println("【银行】: 取钱 " + count + "元");
money -= count;
}
}
}
- 实现
MethodInterceptor
接口
public class ATM implements MethodInterceptor {
/*
拦截器 ATM:实现了 MethodInterceptor 接口,在 intercept 方法中增强逻辑。proxy.invokeSuper(obj, args) 用于调用目标类的真实方法。
*/
@Override
public Object intercept(Object realObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 记录日志:方法调用前
System.out.println("【proxy 前-- atm校验卡号~~】" + method.getName());
// 调用真实方法
Object result = proxy.invokeSuper(realObj, args);
// 记录日志:方法调用后
System.out.println("【proxy 后-- atm吐出银行卡~~】" + method.getName());
return result;
}
}
- User使用
public class CglibMain {
public static void main(String[] args) {
// 创建 CGLIB 的 Enhancer 对象
/*
Enhancer 类:是 CGLIB 动态代理的核心类,用于创建代理对象。通过 setSuperclass 设置目标类,setCallback 设置方法拦截器。
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Bank.class); // 设置目标类
enhancer.setCallback(new ATM()); // 设置方法拦截器
// 创建代理对象
Bank atm = (Bank) enhancer.create();
atm.putMoney(500);
System.out.println("==============");
atm.takMoney(350);
}
}
/*
【proxy 前-- atm校验卡号~~】putMoney
【银行】: 存入 500元
【proxy 后-- atm吐出银行卡~~】putMoney
==============
【proxy 前-- atm校验卡号~~】takMoney
【银行】: 取钱 350元
【proxy 后-- atm吐出银行卡~~】takMoney
*/
2)原理解析
参考自:https://blog.csdn.net/sco5282/article/details/121866799
Bank bank = new Bank();
// 创建代理对象
Bank atm = (Bank) enhancer.create();
System.out.println(bank.getClass());
System.out.println(atm.getClass());
/*
class com.feng.proxy.cglib.Bank
class com.feng.proxy.cglib.Bank$$EnhancerByCGLIB$$21b9b61c 不一样!!!!
*/
从Enhance一步一步往下看。
Enhancer
类是 CGLIB 动态代理的核心类。它用于创建代理类的实例。通过 Enhancer
类,可以设置目标类、回调函数(MethodInterceptor
)以及其他配置项。
Enhancer enhancer = new Enhancer(); // 创建一个对象
enhancer.setSuperclass(Bank.class); // 设置目标类
enhancer.setCallback(new ATM()); // 设置方法拦截器
Enhancer#setSuperclass:
public void setSuperclass(Class superclass) { // 说明cglib还可以支持接口代理的
if (superclass != null && superclass.isInterface()) { // 如果是接口
this.setInterfaces(new Class[]{superclass});
} else if (superclass != null && superclass.equals(class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object)) {
this.superclass = null;
} else { // 这才是设置父类
this.superclass = superclass;
}
}
Enhancer#setCallback:
public void setCallback(Callback callback) {
this.setCallbacks(new Callback[]{callback});
}
public void setCallbacks(Callback[] callbacks) {
if (callbacks != null && callbacks.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
} else {
this.callbacks = callbacks; // 就是设置一个字段的属性
}
}
这个enhancer.create()
方法看起来很关键。点进去看看
// Enhancer.class
public class Enhancer extends AbstractClassGenerator {
public Object create() {
this.classOnly = false;
this.argumentTypes = null;
return this.createHelper(); // 这里
}
private Object createHelper() {
....
// 调用父类的create方法
return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
}
}
//AbstractClassGenerator.class
public abstract class AbstractClassGenerator implements ClassGenerator {
protected Object create(Object key) {
....
if (gen == null) {
byte[] b = this.strategy.generate(this); // 发现有generate字样
...
}
}
}
//DefaultGeneratorStrategy.class
public class DefaultGeneratorStrategy implements GeneratorStrategy {
public byte[] generate(ClassGenerator cg) throws Exception {
DebuggingClassWriter cw = this.getClassVisitor();
/*
transform这个方法名字好熟悉啊,有没有觉得像nio的FileChannel的transferFrom,难道这里说有异曲同工之妙【这里只是乱联想的啊】
*/
this.transform(cg).generateClass(cw); // 生成class
return this.transform(cw.toByteArray()); // 返回字节数组
}
}
//DebuggingClassWriter.class
public class DebuggingClassWriter extends ClassVisitor {
public byte[] toByteArray() {
....
try {
(new File(DebuggingClassWriter.debugLocation + File.separatorChar + dirs)).getParentFile().mkdirs();
//private static String debugLocation = System.getProperty("cglib.debugLocation");
File file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".class");
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
....
}
}
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));
同jdk动态代理部分那样设置一下。就可以看到生成的代理类了。
//动态代理类会重写父类的非 final、private 方法;也会构建自己的方法(cglib 方法)
//cglib 方法的方法体:super.方法名,直接调用父类;
//重写方法:它会调用拦截器中的 intercept() 方法
public class Bank$$EnhancerByCGLIB$$21b9b61c extends Bank implements Factory { // 【区别】继承的Bank类
....
final void CGLIB$putMoney$0(int var1) { // 构建的自己的
super.putMoney(var1);
}
public final void putMoney(int var1) { // 重写的我的
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; // 这不就是在获取方法拦截器吗
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$putMoney$0$Method, new Object[]{new Integer(var1)}, CGLIB$putMoney$0$Proxy);
} else {
super.putMoney(var1);
}
}
final void CGLIB$takMoney$1(int var1) {
super.takMoney(var1);
}
public final void takMoney(int var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$takMoney$1$Method, new Object[]{new Integer(var1)}, CGLIB$takMoney$1$Proxy);
} else {
super.takMoney(var1);
}
}
}
3)思考
参考自:https://blog.csdn.net/starryninglong/article/details/89737419
最开始分析的时候,Enhancer里面有一段这个
if (superclass != null && superclass.isInterface()) { // 如果是接口
this.setInterfaces(new Class[]{superclass});
}
我们来代理这个Subject接口
public interface Subject {
void putMoney( int count );
void takMoney( int count );
}
public class SubjectInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//由于是代理的接口
System.out.println("【cglib】代理接口" + method.getName());
return method.getName();
}
}
//接口代理
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Subject.class);
enhancer.setCallback(new SubjectInterceptor());
Subject subject = (Subject) enhancer.create();
subject.putMoney(100);
subject.takMoney(100);
}
/*
【cglib】代理接口putMoney
【cglib】代理接口takMoney
*/
生成的代理类变成这个样子了
public class Subject$$EnhancerByCGLIB$$1cc519db implements Subject, Factory { // 【区别】实现了Subject接口
}
综上可以看出
对接口进行代理的cglib,最后是实现了该接口和Factory接口
对类进行代理的cglib,最后是继承了类并实现了Factory接口
可以看出,cglib动态代理
可以代理没有接口的类:与 JDK 动态代理不同,CGLIB 可以代理没有实现接口的普通类。
性能较好:由于 CGLIB 直接生成字节码并创建代理类,相较于 JDK 动态代理的反射机制,性能更高。
更强的功能:CGLIB 可以拦截目标类的所有方法,包括 private
和 protected
方法。
但是
无法代理 final
类和 final
方法:由于 CGLIB 需要通过继承目标类来创建代理类,因此无法代理 final
类和 final
方法。
依赖字节码生成:CGLIB 需要依赖字节码生成技术,相对 JDK 动态代理而言,使用起来更加复杂。
④比较
cglib这么好,又可以代理类,还可以代理接口,以后我们动态代理都用cglib吧!!
虽然 CGLIB 动态代理确实非常强大,可以代理普通类(不需要接口)并且性能优于 JDK 动态代理,但 它并不总是最适合所有情况。在选择 JDK 动态代理和 CGLIB 动态代理时,我们需要根据具体的需求来判断,不能一概而论。
- 代理对象是否实现接口:
- JDK 动态代理:只能代理实现了接口的类。如果目标类没有实现接口,JDK 动态代理就无法使用。
- CGLIB 动态代理:可以代理 任何类,即使目标类没有实现接口。CGLIB 会通过继承目标类生成代理类,因此可以代理普通类。
- 性能开销:
- JDK 动态代理:由于 JDK 动态代理是基于反射的,其性能相对较低,特别是在调用频繁的场景中,反射机制带来的开销可能会对性能产生一定影响。
- CGLIB 动态代理:通过字节码技术生成代理类,相比反射来说,CGLIB 的性能通常会更好,尤其在目标类的方法调用较多时。
- 目标类是否为
final
类:
- JDK 动态代理:不受影响,任何类都可以使用 JDK 动态代理。
- CGLIB 动态代理:无法代理
final
类或者final
方法,因为 CGLIB 需要通过继承目标类来生成代理类,而final
类和方法无法被继承和重写。
使用 JDK 动态代理的情况:
- 目标类已经实现了接口,且不需要代理
final
类或final
方法。 - 性能要求不高,或目标方法调用不频繁,JDK 动态代理会更简洁、易于理解和维护。
- 多接口的情况:JDK 动态代理更适合处理多个接口的代理场景。
public interface A {void methodA();}
public interface B {void methodB();}
public class MyClass implements A, B {
@Override
public void methodA() {
System.out.println("method A");
}
@Override
public void methodB() {
System.out.println("method B");
}
}
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before" + method.getName());
Object result = method.invoke(target, args); // 调用目标对象的方法
System.out.println("After" + method.getName());
return result;
}
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
// 创建代理对象,代理 A 和 B 接口
A proxyA = (A) Proxy.newProxyInstance(
MyClass.class.getClassLoader(),
new Class[]{A.class, B.class}, // 多个接口
new MyInvocationHandler(myClass) // InvocationHandler
);
// 代理对象调用方法
proxyA.methodA(); // 调用 A 接口的方法
// 代理对象调用另一个接口的方法
B proxyB = (B) Proxy.newProxyInstance(
MyClass.class.getClassLoader(),
new Class[]{A.class, B.class}, // 多个接口
new MyInvocationHandler(myClass) // InvocationHandler
);
proxyB.methodB(); // 调用 B 接口的方法
}
使用 CGLIB 动态代理的情况:
- 目标类没有实现接口,需要代理一个没有接口的类。
- 性能要求较高,且目标类的方法调用频繁。CGLIB 的性能通常优于 JDK 动态代理。