一、代理模式
概述
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口
主要解决:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层
何时使用:
想在访问一个类时做一些控制
优缺点
优点:
- 职责清晰
- 高扩展性
- 智能化
缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
注意事项
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制
1. 各个角色介绍
1.1 抽象主题(Subject)
- 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题
1.2 真实主题(Real Subject)
- 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问
1.3 代理(Proxy)
- 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等
2. UML图
我们将创建一个 ILandlordService 租房接口和实现了 ILandlordService 接口的实体类。AgentProxy 是一个代理类,减少房东 HostServiceImpl 对象加载的内存占用
3. 具体例子和代码
角色分配
- ILandlordService :租房接口
- HostServiceImpl:房东实现类
- AgentProxy:中介代理
3.1 租房接口及其实现类
- ILandlordService
java
package com.vinjcent.prototype.proxy;
/**
* @author vinjcent
* @description 租房接口
* @since 2024/3/27 17:52
*/
public interface ILandlordService {
/**
* 出租
*
* @param money 金额
*/
void rent(Integer money);
}
- HostServiceImpl
java
package com.vinjcent.prototype.proxy;
/**
* @author vinjcent
* @description 房东实现类(实现)
* @since 2024/3/27 17:55
*/
public class HostServiceImpl implements ILandlordService {
@Override
public void rent(Integer money) {
System.out.println("房东处理...");
System.out.println("出租" + money + "元一个月的房子");
}
@Override
public String toString() {
return "HostServiceImpl{}";
}
}
- AgentProxy
java
package com.vinjcent.prototype.proxy.static_proxy;
import com.vinjcent.prototype.proxy.HostServiceImpl;
import com.vinjcent.prototype.proxy.ILandlordService;
/**
* @author vinjcent
* @description 中介代理
* @since 2024/3/27 18:04
*/
public class AgentProxy implements ILandlordService {
/**
* 被代理的对象
*/
private ILandlordService target;
@Override
public void rent(Integer money) {
if (target == null) {
target = new HostServiceImpl();
}
System.out.println("中介处理...");
target.rent(money);
}
}
3.2 额外拓展(动态代理:JDK动态代理、CGLib动态代理)
3.2.1 JDK动态代理
JDK动态代理的代理类根据目标实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口,JDK动态代理的核心是InvocationHandler接口和Proxy类,缺点是目标类必须有实现接口,如果某个目标类没有实现接口,那么这个类就不能用JDK动态代理
- JDKProxyFactory
java
package com.vinjcent.prototype.proxy.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author vinjcent
* @description JDK动态代理
* @since 2024/3/27 17:24
*/
public class JDKProxyFactory implements InvocationHandler {
/**
* 需要被代理的对象
*/
private final Object object;
public JDKProxyFactory(Object object) {
this.object = object;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
Object o = Proxy.newProxyInstance(
// 当前线程的上下文ClassLoader
Thread.currentThread().getContextClassLoader(),
// 代理对象实现的接口
this.object.getClass().getInterfaces(),
// 处理器自身
this
);
return (T) o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = new Object();
// 进行方法匹配,调用对应方法名的方法
if ("rent".equals(method.getName())) {
System.out.println("JDK动态代理前置增强");
result = method.invoke(object, args);
System.out.println("JDK动态代理后置增强");
}
return result;
}
@Override
public String toString() {
return "JDKProxyFactory{" +
"object=" + object +
'}';
}
}
3.2.2 CGLib动态代理
在程序运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类,CGLib是通过继承的方式实现的动态代理,因此如果某个类被标记为final,它是无法使用CGLib做动态代理的,优点在于不需要实现特定的接口,更加灵活
- CglibProxyFactory
java
package com.vinjcent.prototype.proxy.dynamic_proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author vinjcent
* @description CGLib动态代理
* @since 2024/3/27 22:42:16
*/
public class CglibProxyFactory implements MethodInterceptor {
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
// Enhancer是CGLIB库中用于动态生成子类的主要类.通过创建Enhancer对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类
Enhancer en = new Enhancer();
// 设置代理的父类
en.setSuperclass(clazz);
// 设置方法回调
en.setCallback(this);
Object o = en.create();
// 创建代理实例.通过调用Enhancer对象的create方法,可以生成一个代理对象.代理对象会继承目标类的方法,并且在调用代理对象的方法时会先调用拦截器的intercept方法,再执行目标方法
return (T) en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 在生成代理类时,需要指定拦截器.拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑
Object result = null;
System.out.println("CGLIB动态代理前置增强");
// 通过调用代理对象的方法,会触发拦截器的intercept方法.在intercept方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等
if ("rent".equals(method.getName())) {
// 通过继承的方法实现代理,因此这里调用invokeSuper
result = methodProxy.invokeSuper(o, args);
}
System.out.println("CGLIB动态代理后置增强");
return result;
}
}
3.3 测试主函数
java
package com.vinjcent.prototype.proxy;
import com.vinjcent.prototype.proxy.dynamic_proxy.CglibProxyFactory;
import com.vinjcent.prototype.proxy.dynamic_proxy.JDKProxyFactory;
import com.vinjcent.prototype.proxy.static_proxy.AgentProxy;
/**
* @author vinjcent
* @description 代理模式
* @since 2024/3/27 18:01
*/
public class Main {
public static void main(String[] args) {
System.out.println("静态代理");
// 静态代理
ILandlordService landlordService = new AgentProxy();
System.out.println("第一次代理调用目标类方法");
landlordService.rent(20);
System.out.println("\n第二次代理调用目标类的方法");
landlordService.rent(25);
System.out.println("\n");
HostServiceImpl hostService = new HostServiceImpl();
System.out.println("JDK动态代理");
// JDK动态代理
ILandlordService jdkProxy = new JDKProxyFactory(hostService).getProxy();
jdkProxy.rent(750);
System.out.println("\n");
System.out.println("CGLib动态代理");
// CGLIB动态代理
HostServiceImpl cglibProxy = new CglibProxyFactory().getProxy(hostService.getClass());
cglibProxy.rent(750);
}
}
- 测试结果
4. 使用场景
- 远程代理
- 虚拟代理
- Copy-on-Write 代理
- 保护(Protect or Access)代理
- Cache代理
- 防火墙(Firewall)代理
- 同步化(Synchronization)代理
- 智能引用(Smart Reference)代理