前言
-
技术背景
代理模式是软件设计模式中的一种结构型模式,它在不改变原始接口的前提下,通过引入一个代理类来间接控制对真实对象的访问。这种模式广泛应用在多种场景下,比如远程方法调用、安全性控制、日志记录、性能优化(如缓存)等。 -
为什么使用这个技术
采用代理模式的原因在于它可以为原有对象的操作增加额外的功能或控制流程,例如预处理请求、后处理结果、错误处理以及资源管理等。通过这种方式,可以降低系统耦合度,增强系统的灵活性和可扩展性。 -
应用场景
- 远程服务代理:实现客户端与远程服务器之间的通信,隐藏网络调用细节。
- 权限控制代理:在调用真实对象方法之前进行权限校验。
- 缓存代理:将常用数据缓存在代理中,减少直接操作源对象的频率。
- 延迟加载代理:对于耗时的数据加载任务,仅在真正需要时才执行。
- 日志记录代理:在方法调用前后插入日志记录代码,便于追踪和分析用户行为。
- 同步/异步代理:转换同步调用为异步调用,提高系统的响应能力。
一、定义
代理模式的原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。
从这个定义中我们能看出,代理模式是作为对象之间的一种中间结构来使用的,通过构建一个代理对象来对原始的功能进行委托处理,其中有一个很重要的功能就是控制对象的访问。拿现实中的例子来说,假设你有一套房子要卖,可你却正好在外地出差,你不能亲自处理带人看房、过户等购房手续,这时你就可以找一个房产中介来作为你的代理人,委托他来帮你带人看房、处理过户手续。这就是现实中经典的代理模式的例子。
我们先来看看代理模式的 UML 图:
通过UML图直观展示了其核心组成部分:
-
抽象主题类(RealObject):定义了所有代理对象和真实对象共有的接口,提供客户端使用的统一入口。
-
主题实现类(RealObjectImpl):实现了抽象主题的具体业务逻辑。
-
代理类(Proxy):同样实现了抽象主题接口,并持有真实主题的引用,在执行真实主题功能前后添加附加行为。
在图中这三个角色都有相互依赖的关系,代理类采用继承的方式来获取抽象主题类的公共方法定义,在代理类内可以进行相关的扩展操作,但最终还是需要执行主题实现类的方法。
二、静态代理
1、UML 对应的代码实现
public interface RealObject {
void doSomething();
}
public class RealObjectImpl implements RealObject {
@Override
public void doSomething() {
System.out.println("=== 真实对象正在执行...");
}
}
public class Proxy implements RealObject {
private RealObject realObject;
public Proxy(RealObject realObject) {
this.realObject = realObject;
}
@Override
public void doSomething() {
// 前置操作
System.out.println("== 代理类开始执行额外操作");
// 调用真实对象的方法
realObject.doSomething();
// 后置操作
System.out.println("== 代理类完成额外操作");
}
}
// 单元测试
public class Demo {
public static void main(String[] args) {
RealObject realObject = new RealObjectImpl();
RealObject proxyObject = new Proxy(realObject);
proxyObject.doSomething();
}
}
在上面的这段代码实现中,RealObjectImpl 实现了接口 RealObject 的功能 doSomething,这时我们又创建了一个代理对象 Proxy,它继承了 RealObjectImpl,目的是在使用 RealObject 时可以做一些额外的操作。
2、静态代理的缺点
- 扩展性有限:每个具体主题都需要创建对应的代理类,如果主题数量多会增加代码量和维护成本。
- 灵活性不足:一旦接口发生改变,所有的代理类都需要相应修改。
三、动态代理
1、什么是JDK动态代理
JDK动态代理利用Java反射机制,在运行时动态生成代理类和实例,无需手动编写每个代理类。
2、代码实现
public interface Icrud {
void add();
void delete();
void update();
void select();
}
//需要被代理的对象
public class UserMapper implements Icrud{
@Override
public void add() {
System.out.println("增加了数据");
}
@Override
public void delete() {
System.out.println("删除了数据");
}
@Override
public void update() {
System.out.println("更新了数据");
}
@Override
public void select() {
System.out.println("选择了数据");
}
}
//利用JDK动态代理,生成UserMapper的代理类,帮助实现功能的同时,方便功能扩展
// 此类是负责生成代理对象的类,不是代理类
public class ProxyUserMapper{
private Object target;
//设置被代理的对象
public void setTarget(Object target) {
this.target = target;
}
//返回代理类对象
public Object getProxy(){
return Proxy.newProxyInstance(ProxyUserMapper.class.getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
});
}
/* //利用反射的原理,方法实现时,可以插入其他逻辑,但不影响原方法的实现
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}*/
public void log(String msg){
System.out.println("要执行" + msg + "方法");
}
}
public class Demo01 {
public static void main(String[] args) {
UserMapper userMapper = new UserMapper();
ProxyUserMapper proxyUserMapper = new ProxyUserMapper();
proxyUserMapper.setTarget(userMapper);
Icrud proxy = (Icrud) proxyUserMapper.getProxy();
proxy.update();
System.out.println("=========================");
proxy.delete();
}
}
3、测试结果
四、优缺点
优点:
- 解耦了客户端和服务端,增强了灵活性和可扩展性。
- 支持延迟初始化、权限控制、性能优化等多种应用场景。
- 可以通过代理灵活地控制对象生命周期和方法调用过程。
缺点:
- 由于引入了代理层,增加了系统的复杂性和间接开销。
- 在某些情况下,动态代理的性能消耗可能大于直接调用。
- 对于复杂的代理逻辑实现,可能会导致代码难以理解和维护。
总结
总之,代理模式通过引入代理对象这一中间层,巧妙地解决了无法直接调用对象、需要添加额外功能、性能优化及权限控制等一系列问题,成为现代软件开发中的重要设计工具之一。