🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解建造者模式请看: (五)趣学设计模式 之 建造者模式!
🌟同时我的 JDK动态代理 vs CGLIB:一场经纪人之战,谁才是你的最佳选择?也不错
一、啥是代理模式?
代理模式,就像找了个替身 👯! 你想做一件事情,但是自己不想做,或者不能做,就找个代理人(替身)来帮你做 🤝。 代理人可以帮你处理一些额外的事情,比如权限控制 👮、日志记录 📝、缓存 📦 等等,让你更专注于核心业务 🎯!
简单来说,就是你不想直接面对某个对象,就找个代理来帮你!
- 你想做的事情比较敏感: 就像访问一些需要权限的资源 🔑,你需要先验证身份,才能访问!
- 你想做的事情比较耗时: 就像下载一个大文件 💾,你需要等待很长时间,才能完成!
- 你想做的事情比较复杂: 就像购买机票 ✈️,你需要比较不同的航班、价格和时间,才能做出选择!
二、为什么要用代理模式?
用代理模式,好处多多 👍:
- 保护目标对象: 代理可以控制对目标对象的访问,防止恶意操作 🛡️! 就像保镖保护明星 🌟,防止粉丝的疯狂行为!
- 增强目标对象的功能: 代理可以在目标对象执行前后做一些额外的事情,比如日志记录、性能监控 📈! 就像经纪人帮明星安排行程、处理事务,让明星更专注于表演!
- 延迟加载: 代理可以在需要的时候才创建目标对象,节省资源 ⏳! 就像懒加载图片 🖼️,只有当图片出现在屏幕上时,才加载图片!
- 远程代理: 代理可以代表远程对象,让你像访问本地对象一样访问远程对象 🌐! 就像 VPN,让你访问被墙的网站!
- 降低耦合度: 客户端不需要直接依赖目标对象,只需要依赖代理对象,降低了系统的耦合度 🔗!
三、代理模式的实现方式
代理模式主要分为三种:
- 静态代理: 代理类在编译时就已经确定,就像提前找好的替身演员 🎬!
- JDK动态代理: 代理类在运行时动态生成,需要实现接口,就像临时找来的替身演员 🎭!
- CGLIB动态代理: 代理类在运行时动态生成,不需要实现接口,通过继承实现,就像 AI 换脸技术 🤖!
1. 静态代理
静态代理,顾名思义,代理类是提前写好的,就像提前找好的替身演员,随时可以上场 💃!
案例:火车站卖票(经典案例 🚂)
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦 😫。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了 😊。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。
代码示例:
// 卖票接口
public interface SellTickets {
void sell(); // 卖票
}
// 火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票"); // 卖票
}
}
// 代售点
public class ProxyPoint implements SellTickets {
private TrainStation station = new TrainStation(); // 持有火车站对象的引用
public void sell() {
System.out.println("代理点收取一些服务费用"); // 增强功能
station.sell(); // 调用火车站的卖票方法
}
}
// 测试类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint(); // 创建代理对象
pp.sell(); // 调用代理对象的卖票方法
}
}
分析:
从上面代码中可以看出,测试类直接访问的是 ProxyPoint
类对象,也就是说 ProxyPoint
作为访问对象和目标对象的中介 🤝。同时也对 sell
方法进行了增强(代理点收取一些服务费用 💰)。
输出结果:
代理点收取一些服务费用
火车站卖票
2. JDK动态代理
JDK动态代理,代理类是在运行时动态生成的,需要实现接口,就像临时找来的替身演员,需要会表演 💃!
案例:还是火车站卖票(升级版 🚂)
还是上面的火车站卖票的例子,但是这次我们使用JDK动态代理来实现 🚀!
代码示例:
// 卖票接口
public interface SellTickets {
void sell(); // 卖票
}
// 火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票"); // 卖票
}
}
// 代理工厂,用来创建代理对象
public class ProxyFactory {
private TrainStation station = new TrainStation(); // 持有火车站对象的引用
public SellTickets getProxyObject() {
// 使用Proxy获取代理对象
/*
newProxyInstance()方法参数说明:
ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
InvocationHandler h : 代理对象的调用处理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method 实例
args : 代理对象调用接口方法时传递的实际参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); // 增强功能
// 执行真实对象
Object result = method.invoke(station, args); // 调用火车站的卖票方法
return result; // 返回结果
}
});
return sellTickets; // 返回代理对象
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject(); // 获取代理对象
proxyObject.sell(); // 调用代理对象的卖票方法
}
}
分析:
使用了动态代理,我们思考下面问题 🤔:
-
ProxyFactory
是代理类吗? 🙅ProxyFactory
不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类 💫。 -
动态代理的执行流程是什么样? ➡️
- 客户端调用代理对象的
sell()
方法。 - 代理对象调用
InvocationHandler
的invoke()
方法。 invoke()
方法中,我们可以增强功能,比如收取服务费用。invoke()
方法中,通过反射调用真实对象的sell()
方法。- 真实对象执行
sell()
方法,返回结果。 invoke()
方法将结果返回给代理对象。- 代理对象将结果返回给客户端。
- 客户端调用代理对象的
输出结果:
代理点收取一些服务费用(JDK动态代理方式)
火车站卖票
3. CGLIB动态代理
CGLIB动态代理,代理类是在运行时动态生成的,不需要实现接口,通过继承实现,就像 AI 换脸技术,直接把你的脸换成别人的脸 🎭!
案例:还是火车站卖票(终极版 🚂)
还是上面的火车站卖票的例子,但是这次我们使用CGLIB动态代理来实现 🚀! 如果没有定义 SellTickets
接口,只定义了 TrainStation
(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理 😫。
代码示例:
// 火车站
public class TrainStation {
public void sell() {
System.out.println("火车站卖票"); // 卖票
}
}
// 代理工厂
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 ProxyFactory implements MethodInterceptor {
private TrainStation target = new TrainStation(); // 持有火车站对象的引用
public TrainStation getProxyObject() {
// 创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer(); // 创建 Enhancer 对象
// 设置父类的字节码对象
enhancer.setSuperclass(target.getClass()); // 设置父类
// 设置回调函数
enhancer.setCallback(this); // 设置回调
// 创建代理对象
TrainStation obj = (TrainStation) enhancer.create(); // 创建代理对象
return obj; // 返回代理对象
}
/*
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); // 增强功能
Object result = methodProxy.invokeSuper(o, args); // 调用火车站的卖票方法
return result; // 返回结果
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 创建代理工厂对象
ProxyFactory factory = new ProxyFactory(); // 创建代理工厂
// 获取代理对象
TrainStation proxyObject = factory.getProxyObject(); // 获取代理对象
proxyObject.sell(); // 调用代理对象的卖票方法
}
}
分析:
CGLIB动态代理不需要接口,它是通过继承来实现的。
ProxyFactory
创建了一个Enhancer
对象,类似于 JDK 动态代理的Proxy
类。enhancer.setSuperclass(target.getClass())
设置父类为目标对象。enhancer.setCallback(this)
设置回调函数为MethodInterceptor
接口的实现类,也就是ProxyFactory
本身。enhancer.create()
创建代理对象,实际上是创建了一个目标对象的子类。- 当调用代理对象的
sell()
方法时,会调用MethodInterceptor
接口的intercept()
方法。 - 在
intercept()
方法中,我们可以增强功能,比如收取服务费用。 methodProxy.invokeSuper(o, args)
调用父类(目标对象)的sell()
方法。
输出结果:
代理点收取一些服务费用(CGLIB动态代理方式)
火车站卖票
四、三种代理的对比
我的文章: JDK动态代理 vs CGLIB:一场经纪人之战,谁才是你的最佳选择?讲的更好一点,可以看看
-
JDK代理 vs CGLIB代理:
特性 JDK代理 CGLIB代理 接口 必须实现接口 不需要实现接口 实现方式 通过 Proxy.newProxyInstance()
动态生成代理类通过继承目标类动态生成子类 性能 相对较慢 相对较快 使用场景 目标对象实现了接口 目标对象没有实现接口 依赖 JDK自带,无需额外依赖 需要引入 CGLIB 库 -
动态代理 vs 静态代理:
特性 静态代理 动态代理 代理类 编译时确定 运行时动态生成 灵活性 较低 较高 代码量 较大 较小 维护性 较差 较好 适用场景 代理对象数量较少,代理逻辑比较固定 代理对象数量较多,代理逻辑比较灵活
五、代理模式的优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用 🛡️! 就像保镖保护明星,防止粉丝的疯狂行为!
- 代理对象可以扩展目标对象的功能 📈! 就像经纪人帮明星安排行程、处理事务,让明星更专注于表演!
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度 🔗! 就像中间商,连接买家和卖家,让双方不用直接接触!
- 符合开闭原则,可以在不修改目标对象的情况下,增加新的代理类,扩展功能 🆕!
缺点:
- 增加了系统的复杂度 😫! 需要创建多个类,代码量比较大!
- 可能会降低性能 🐌! 代理对象会增加额外的开销,可能会影响性能!
六、代理模式的应用场景
- 远程代理: 就像 VPN,让你访问被墙的网站 🌐!
- 虚拟代理: 就像懒加载图片 🖼️,只有当图片出现在屏幕上时,才加载图片!
- 保护代理: 就像访问一些需要权限的资源 🔑,你需要先验证身份,才能访问!
- 缓存代理: 就像 CDN,缓存静态资源,提高访问速度 🚀!
- 防火墙代理: 就像防火墙,保护你的电脑免受病毒攻击 🛡️!
- 事务代理: 控制数据库事务的提交和回滚 💱!
七、总结
- 代理模式就像找了个替身,帮你做一些事情! 👯
- 主要分为静态代理、JDK动态代理和CGLIB动态代理三种! 🎭
- 优点是保护目标对象、扩展功能、降低耦合度、符合开闭原则! 👍
- 缺点是增加复杂度、可能降低性能! 👎
- 适用于需要控制对目标对象的访问、增强目标对象的功能、延迟加载、远程代理等场景! 🎯