系列文章目录
肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门
文章目录
- 系列文章目录
- 前言
- 一、什么是代理模式
- 二、静态代理
- 三、动态代理
- 1. JDK动态代理
- 2. CGLib动态代理
- 写在最后
前言
本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式和装饰器模式,本节我们来学习一下——代理模式。
一、什么是代理模式
代理模式(Proxy Pattern),用于在对象之间提供间接访问,在代理模式中,代理对象充当了原始对象的代表,以控制对原始对象的访问。
代理模式的主要角色:
-
抽象主题(Subject):定义了代理类和原始类之间的公共接口,以便代理类可以替代原始类。抽象主题通常是一个接口或抽象类,其中定义了原始对象和代理对象需要实现的方法。
-
具体主题(Real Subject):是代理模式中的原始对象,代理类所代表的对象。具体主题实现了抽象主题中定义的接口,是真正执行业务逻辑的对象。
-
代理(Proxy):代理是客户端访问具体主题的中介。代理对象与具体主题实现相同的接口,并保存具体主题的引用。当客户端向代理对象发送请求时,代理对象会将请求转发给具体主题,并可以在请求前后添加额外的逻辑。
概念了解了以后,其实不难理解,生活当中就有很多代理模式的样例,举个栗子,韩梅梅和李雷是同学,韩梅梅饿了但又不想自己出去吃饭,就想让李雷去买回来。
这里李雷就相当于是代理角色。
二、静态代理
代理模式又分为静态代理和动态代理,先说静态代理
写下代码:
先定义一个顶层接口,定义买饭这件事
public interface IPerson {
void findFood();
}
韩梅梅饿了要吃饭
public class HanMeiMei implements IPerson {
@Override
public void findFood() {
System.out.println("韩梅梅饿了想找点吃的");
}
}
但韩梅梅有点懒,想让李雷帮忙买回来
public class LiLei implements IPerson {
private HanMeiMei hanMeiMei;
public LiLei(HanMeiMei hanMeiMei) {
this.hanMeiMei = hanMeiMei;
}
@Override
public void findFood() {
System.out.println("李雷接到韩梅梅的电话");
hanMeiMei.findFood();
System.out.println("李雷帮韩梅梅买点吃的回来");
}
}
测试一下:
public class Test {
public static void main(String[] args) {
LiLei liLei = new LiLei(new HanMeiMei());
liLei.findFood();
}
}
静态代理是在编译时就已经确定代理关系的代理模式。它需要为每个原始对象编写一个代理类,代理类与原始类实现相同的接口,以便可以通过代理类访问原始对象。在代理类中,可以通过调用原始对象的方法来实现对原始对象的访问,并可以在方法前后添加额外的逻辑。
三、动态代理
上述场景里,韩梅梅的主要诉求是饿了想吃饭,如果当时李雷没联系上,就还得联系别人,代码角度就还需要新增一个代理类,这样的话显然使用静态代理就不太适合了,目的是买到食物,具体谁来买其实并不重要,这就引申出来动态代理的概念。
在Java中,目前普遍使用的是JDK动态代理和CGLib动态代理
1. JDK动态代理
JDK动态代理是指使用Java内置的反射机制来动态生成代理对象的一种代理模式实现方式。
JDK动态代理需要满足以下两个条件:
-
被代理类必须实现至少一个接口。
-
代理类必须实现InvocationHandler接口。
InvocationHandler接口中只定义了一个方法invoke(),这个方法会在代理对象调用方法时被自动调用。在invoke()方法中,我们可以根据方法名和参数类型等信息,决定是否要将方法调用转发给被代理对象,或者在调用前后添加一些额外的逻辑。
我们来修改下代码:
首先先修改一下代理类
public class WaiMai implements InvocationHandler {
private HanMeiMei hanMeiMei;
public WaiMai(HanMeiMei hanMeiMei) {
this.hanMeiMei = hanMeiMei;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("拿起手机");
Object result = method.invoke(hanMeiMei, args);
System.out.println("在外卖平台下单,小哥开始配送");
return result;
}
}
测试一下:
public class Test {
public static void main(String[] args) {
HanMeiMei hanMeiMei = new HanMeiMei();
IPerson proxyPerson = (IPerson) Proxy.newProxyInstance(
hanMeiMei.getClass().getClassLoader(),
hanMeiMei.getClass().getInterfaces(),
new WaiMai(hanMeiMei)
);
proxyPerson.findFood();
}
}
newProxyInstance()方法会动态生成一个代理类,该方法有三个参数:
- ClassLoader:生成一个类, 这个类也需要加载到方法区中, 因此需要指定ClassLoader来加载该类
- Class[] interfaces:要实现的接口
- InvocationHandler:调用处理器
2. CGLib动态代理
CGLib动态代理是指使用CGLib库来动态生成代理对象的一种代理模式实现方式。
相比于JDK动态代理,它可以代理没有实现任何接口的类,因为它是通过继承被代理类来生成代理对象的。
CGLib动态代理的实现过程是:通过ASM字节码框架直接将代理对象类的class文件加载到JVM中,修改其字节码生成子类,子类重写父类中的方法,并在重写的方法中增加了我们定义的逻辑。最后,生成一个新的代理对象子类的class文件,然后通过反射机制来创建代理对象。
修改下代码:
首先修改一下代理类,不在需要实现顶层接口
public class HanMeiMei {
public void findFood() {
System.out.println("韩梅梅饿了想找点吃的");
}
}
先新增一个实现MethodInterceptor接口的实现类
public class WaiMai implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("拿起手机");
Object result = proxy.invokeSuper(obj, args);
System.out.println("在外卖平台下单,小哥开始配送");
return result;
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HanMeiMei.class);
enhancer.setCallback(new WaiMai());
HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();
proxyHanMeiMei.findFood();
}
}
CGLib动态代理,可以选择使用反射调用或者FastClass机制调用,默认情况下会使用反射调用,如果需要使用FastClass机制调用,则需要通过设置Enhancer类的useFastClass属性来开启。
使用FastClass机制:
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HanMeiMei.class);
enhancer.setCallback(new WaiMai());
enhancer.setUseFactory(false); // 禁用缓存,强制使用FastClass机制
enhancer.setUseFastClass(true); // 开启FastClass机制
HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();
proxyHanMeiMei.findFood();
}
}
使用FastClass机制调用时,CGLib会通过ASM字节码框架生成一个FastClass类,该类中包含被代理类中方法的索引和对应的方法调用代码,这样在调用代理类的方法时,就可以直接使用FastClass类中对应方法的调用代码,避免了反射调用的开销。
需要注意的是,使用FastClass机制调用虽然能够提高代理类的性能,但也会增加代理类生成的时间和内存开销。
上文所描述的是CGLib 3.0.0 版本之前,想开启 FastClass 机制,需要手动调用setUseFastClass()方法来设置。
CGLib 3.0.0 版本开始默认启用 FastClass 机制,无需调用setUseFastClass()方法。
写在最后
代理模式的基本思想是创建一个代理对象,该代理对象与原始对象具有相同的接口,以便可以替换原始对象。当客户端向代理对象发送请求时,代理对象会将请求转发给原始对象,同时可以在请求前后添加额外的逻辑。这种方式可以隐藏原始对象的复杂性,并提供更加简单和易用的接口。
代理模式的优点:
- 解耦原有对象,代理对象与原有对象之间的耦合度降低,原有对象可以专注于自己的业务逻辑,而代理对象则负责其他方面的操作。
- 对原有对象进行增强,代理对象可以在调用原有对象方法前后进行一些操作,例如日志记录、缓存处理等,从而增强了原有对象的功能。
静态代理的优点:
- 在编译时进行类型检查,避免了运行时出现类型错误的风险
- 提供更好的性能,因为代理类在编译时就已经生成,不需要在运行时动态生成代理对象
静态代理的缺点:
- 需要为每个原始对象编写一个代理类,如果原始对象的接口发生变化,代理类也需要相应地进行更新
动态代理的优点:
- 可以动态地生成代理对象,避免了静态代理中需要为每个原始对象编写代理类的麻烦
- 可以支持对不同的原始对象进行代理,并可以在运行时动态地添加或删除代理对象
动态代理的缺点:
- JDK动态代理和CGLib动态代理(反射调用)需要通过反射机制动态生成代理类,可能会降低一些性能,CGLib动态代理(FastClass调用)虽然避免了反射调用的开销,但会增加代理类生成的时间和内存开销,同样会影响性能
- 只能代理公共方法,不能代理私有方法和final方法