代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
小提示:文中所述真实类,原始类,目标类,是一个意思,都是指被代理角色。
代理模式的原理是将真实类的功能封装在代理类中,最基本的方式就是创建一个代理类,代理类实现和真实类相同的接口,并且在代理类中引用真实类的实例。这样就可以在不修改真实类代码的情况下,通过代理类来控制对真实类的访问。代理类还可以在调用真实类的方法前后添加一些额外的逻辑,来实现对真实类的访问控制、缓存、日志记录等功能。
实现代理模式有两种方案:静态代理和动态代理。静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。动态代理则是在运行时动态生成代理类。
动态代理方案有两种实现:其一,通过Java本身自带 java.lang.reflect.Proxy
类来实现动态代理功能。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类。二者底层实现上都应用了反射和操作字节码技术。
JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。
CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。
代理模式主要适用于需要控制、增强或隐藏对象访问的场景。适合代理访问的具体场景如下:
实现方式比较简单,很容易理解,直接上代码
NO.1 抽象接口:定义视频播放器接口Player
public interface Player {
void loadVideo(String filename);
void playVideo(String filename);
}
NO.2 真实类:定义接口实现类VPlayer
public class VPlayer implements Player {
@Override
public void loadVideo(String filename) {
System.out.println("加载MP4视频文件:"+filename);
}
@Override
public void playVideo(String filename) {
System.out.println("播放MP4视频:"+filename);
}
}
NO.3 代理类:定义代理类VPlayerProxy,实现同样的接口
public class VPlayerProxy implements Player {
private Player player;
public VPlayerProxy(Player player) {
this.player = player;
}
@Override
public void loadVideo(String filename) {
player.loadVideo(filename);
}
@Override
public void playVideo(String filename) {
player.playVideo(filename);
}
}
NO.4 客户端调用
public class Client1 {
public static void main(String[] args) {
//直连方式
Player vplay=new VPlayer();
vplay.playVideo("aaa.mp4");
System.out.println();
//代理方式
Player proxy=new VPlayerProxy(vplay);
proxy.loadVideo("aaa.mp4");
proxy.playVideo("aaa.mp4");
}
}
JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:
通用实现代码
public class JDKProxyFactory implements InvocationHandler {
//需要被代理的对象
private Object object;
public JDKProxyFactory(Object object) {
this.object = object;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),//当前线程的上下文ClassLoader
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 class Client2 {
public static void main(String[] args) {
Player player=new VPlayer();
Player proxy=new JDKProxyFactory(player).getProxy();
proxy.loadVideo("aaa.mp4");
proxy.playVideo("aaa.mp4");
/* 或者
Player p=new VPlayer();
Player o = (Player) Proxy.newProxyInstance(
p.getClass().getClassLoader(),
p.getClass().getInterfaces(),
new VPlayerProxyFactory(p)
);
o.loadVideo("aaaa.mp4");
*/
}
}
CGLIB(Code Generation Library)是一个基于ASM(Java字节码操作框架)实现的代码生成库,它可以在运行时动态生成目标类的子类作为代理类,并覆盖其中的方法来实现代理功能。与Java自带的JDK动态代理不同,CGlib动态代理可以代理没有实现接口的类。其原理分为以下几个步骤:
单独定义一个没有接口的真实类APlayer
//音频播放器
public class APlayer {
public void loadAudio(String filename) {
System.out.println("加载MP3音频文件:"+filename);
}
public void playAudio(String filename) {
System.out.println("播放MP3:"+filename);
}
}
通用实现代码
public class CglibProxyFactory implements MethodInterceptor {
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
Enhancer en = new Enhancer();
//设置代理的父类
en.setSuperclass(clazz);
//设置方法回调
en.setCallback(this);
//创建代理实例
return (T)en.create();
}
@Override
//参数中的object是目标对象,method和args是目标对象的方法和参数,methodProxy是方法代理
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
if ("loadAudio".equals(method.getName())) {
//通过继承的方法实现代理,因此这里调用invokeSuper
result = methodProxy.invokeSuper(object, args);
}
if ("playAudio".equals(method.getName())) {
result = methodProxy.invokeSuper(object, args);
}
return result;
}
}
客户端调用
public class Client3 {
public static void main(String[] args) {
APlayer aplayer=new APlayer();
APlayer proxy = new CglibProxyFactory().getProxy(aplayer.getClass());
//验证代理类的父类
System.out.println("代理类的父类:"+proxy.getClass().getSuperclass().getSimpleName());
System.out.println();
proxy.loadAudio("荷塘月色.mp3");
proxy.playAudio("荷塘月色.mp3");
}
}
代理模式可以在不修改真实类代码的情况下,实现对真实类的访问控制、性能优化等功能。Java 中有两种实现代理模式的方法:静态代理和动态代理。静态代理需要在编译之前手动编写代理类,而动态代理可以在运行时动态生成代理类。
其中动态代理又分为JDK动态代理和CGlib动态代理,一般情况下我们使用前者,当代理类没有接口时选用后者作为前者的补充方案。代理模式的优点包括降低系统耦合度、增强代码可扩展性、提高系统安全性和简化接口,但它也会增加系统的复杂性和可能影响性能。在需要控制访问、远程代理、功能增强等场景下,代理模式是一个很好的选择。