1、概述
代理模式(Proxy Pattern)属于结构型设计模式,其核心思想是为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,并可以在不改变客户端代码的情况下增强或控制对象的访问。
代理模式主要分为三种类型:
静态代理:由程序员创建或第三方工具生成代理类的源代码,再进行编译。 其代理类和委托类在编译期间就确定下来。
动态代理: 代理类在程序运行时通过反射等机制动态生成,无需程序员显式地创建代理类。
JDK 动态代理
CGLib 动态代理
虚拟代理: 当有一个需要创建开销较大的对象时,通过虚拟代理来存放实例化需要较长时间的真实对象。
2、优缺点
优点:
控制访问:代理模式可以控制对对象的访问,可以在访问对象之前或之后执行一些额外的操作。例如:日志记录、功能增强、权限验证、性能监控等。
保护对象:代理模式可以对真实对象进行保护,只允许特定的客户端访问真实对象,从而提供了一定的安全性。
延迟加载:代理模式可以延迟加载真实对象,当需要真实对象时才进行创建和初始化,从而提高了系统的性能和资源利用率。
简化客户端:代理模式隐藏了真实对象的复杂性,客户端可以通过代理对象来完成操作,无需直接与真实对象交互。
缺点:
增加复杂性:因为引入了代理对象,使得对对象的管理和维护更加复杂。
性能损耗:由于代理模式在访问对象时引入了额外的间接层,可能会导致性能上的损耗。每次通过代理访问对象都需要经过额外的处理步骤,可能会对系统的响应时间产生影响。
3、实现方式
3.1、静态代理
//测试类
public class Test {
public static void main(String[] args) {
// 直接调用
RealPlayer realPlayer = new RealPlayer();
realPlayer.playVideo("《西游记》.mp4");
System.out.println("===========================");
// 代理方式调用,代理真实对象
PlayerProxy playerProxy = new PlayerProxy(realPlayer);
playerProxy.loadVideo("《三国演义》.mp4");
playerProxy.playVideo("《三国演义》.mp4");
}
}
public interface Player {
void loadVideo(String filename);
void playVideo(String filename);
}
public class RealPlayer implements Player {
@Override
public void loadVideo(String filename) {
System.out.println("加载MP4视频文件: " + filename);
}
@Override
public void playVideo(String filename) {
System.out.println("播放MP4视频文件: " + filename);
}
}
public class PlayerProxy implements Player{
private RealPlayer realPlayer;
public PlayerProxy(RealPlayer realPlayer){
this.realPlayer = realPlayer;
}
@Override
public void loadVideo(String filename) {
realPlayer.loadVideo(filename);
}
@Override
public void playVideo(String filename) {
realPlayer.playVideo(filename);
}
}
3.2、JDK动态代理
JDK 动态代理可以在运行时动态生成一个代理对象,是 Java 标准库中提供的一种代理方法,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK 动态代理通过反射机制实现代理功能。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//测试类
public class Test {
public static void main(String[] args) {
// 直接调用
RealPlayer realPlayer = new RealPlayer();
//realPlayer.playVideo("《西游记》.mp4");
System.out.println("===========================");
// 代理方式调用,代理真实对象
Player proxy = (Player) new JDKProxyFactory(realPlayer).getProxy();
proxy.loadVideo("《三国演义》.mp4");
proxy.playVideo("《三国演义》.mp4");
}
}
public class JDKProxyFactory implements InvocationHandler {
// 需要被代理的对象
private Object object;
public JDKProxyFactory(Object object) {
this.object = object;
}
@SuppressWarnings("unchecked")
public Object getProxy() {
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 进行方法匹配,调用对应方法名的方法
if ("loadVideo".equals(method.getName())) {
result = method.invoke(object, args);
}
if ("playVideo".equals(method.getName())) {
System.out.println("前置增强");
result = method.invoke(object, args);
System.out.println("后置增强");
}
return result;
}
}
public interface Player {
void loadVideo(String filename);
void playVideo(String filename);
}
public class RealPlayer implements Player {
@Override
public void loadVideo(String filename) {
System.out.println("加载MP4视频文件: " + filename);
}
@Override
public void playVideo(String filename) {
System.out.println("播放MP4视频文件: " + filename);
}
}
public class PlayerProxy implements Player{
private RealPlayer realPlayer;
public PlayerProxy(RealPlayer realPlayer){
this.realPlayer = realPlayer;
}
@Override
public void loadVideo(String filename) {
realPlayer.loadVideo(filename);
}
@Override
public void playVideo(String filename) {
realPlayer.playVideo(filename);
}
}
3.3、CGLib动态代理
需要引入第三方 CGLIB 包
4、应用场景
远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。