# 代理模式(Proxy Pattern)
代码地址
- Github:DesignPattern/src/main/java/com/design/proxy (opens new window)
- Gitee(码云):DesignPattern/src/main/java/com/design/proxy (opens new window)
# 1. 介绍
代理模式为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到了中介的作用
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式
# 1.1. 特点
待补充
# 1.2. 结构
待补充
# 1.3. 优缺点
- 意图:为其他对象提供一种代理以控制对这个对象的访问
- 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层,不修改源码的基础上
- 何时使用:想在访问一个类时做一些控制
- 如何解决:增加中间层
- 关键代码:实现与被代理类组合
- 优点:
- 将代理对象和真实被调用的目标对象分离,职责清晰
- 降低耦合,高扩展性
- 保护目标对象,增强目标对象
- 缺点:
- 由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
- 造成类的数目增加,增加复杂度
- 应用场景:
- 买火车票不一定在火车站买,也可以去代售点
- 远程代理,虚拟代理等
- Windows 里面的快捷方式
- Spring AOP
区别
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制
# 2. 实现
分为静态代理和动态代理,动态代理一般实现方式就是 JDK 代理和 CgLib 代理,如下我们针对下面两个类进行代理处理,音乐媒体类是实现了接口,电影媒体类未实现接口
- 音乐媒体类
/**
* 媒体接口
*/
public interface Media {
void play(String file);
}
/**
* 实现媒体接口 - 音乐媒体
*/
public class MusicMediaImpl implements Media {
@Override
public void play(String file) {
System.out.println("Play Music: " + file);
}
}
- 电影媒体类
/**
* 直接的电影媒体类
*/
public class MovieMedia {
public void play(String file) {
System.out.println("Play Movie: " + file);
}
}
# 2.1. 静态代理
通过在代码中显式地定义了一个代理类,在代理类中通过同名的方法对目标对象的方法进行包装,客户端通过调用代理类的方法来调用目标对象的方法,如下针对音乐媒体类进行两次代理
/**
* 静态代理一
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:43
*/
public class ProxyMediaImpl implements Media {
Media media;
public ProxyMediaImpl(Media media) {
this.media = media;
}
@Override
public void play(String file) {
System.out.println("Start");
media.play(file);
System.out.println("End");
}
}
/**
* 静态代理二
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:43
*/
public class TimeProxyMediaImpl implements Media {
Media media;
public TimeProxyMediaImpl(Media media) {
this.media = media;
}
@Override
public void play(String file) {
System.out.println(System.currentTimeMillis());
media.play(file);
System.out.println(System.currentTimeMillis());
}
}
/**
* 静态代理事先知道要代理的是什么,目标角色固定不灵活
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:41
*/
public class Main {
public static void main(String[] args) {
// 组合嵌套多个代理类
Media musicMedia = new MusicMediaImpl();
// musicMedia.play("音乐");
Media proxyMedia = new ProxyMediaImpl(musicMedia);
// proxyMedia.play("音乐");
Media timeProxyMedia = new TimeProxyMediaImpl(proxyMedia);
timeProxyMedia.play("音乐");
}
}
1675155078395
Start
Play Music: 音乐
End
1675155078396
# 2.2. 动态代理
使用 JDK 代理和 CgLib 代理实现
- JDK 代理
/**
* JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求
* 1. 实现InvocationHandler接口,重写invoke()
* 2. 使用Proxy.newProxyInstance()产生代理对象
* 3. 被代理的对象必须要实现接口
*
* JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类
* JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
* JDK创建代理对象效率较高,执行效率较低
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:52
*/
public class JdkDynamicProxy implements InvocationHandler {
/**
* 代理的目标对象
*/
private Object object;
public JdkDynamicProxy(Object object) {
this.object = object;
}
public Object proxy() {
Class<?> clazz = object.getClass();
// 生成代理对象
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod(object);
Object result = method.invoke(object, args);
afterMethod(object);
return result;
}
private void beforeMethod(Object object) {
if (object instanceof MusicMediaImpl) {
System.out.println("MusicMediaImpl Start");
} else if (object instanceof MovieMedia) {
System.out.println("MovieMedia Start");
} else {
throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
}
}
private void afterMethod(Object object) {
if (object instanceof MusicMediaImpl) {
System.out.println("MusicMediaImpl End");
} else if (object instanceof MovieMedia) {
System.out.println("MovieMedia End");
} else {
throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
}
}
}
- CgLib 代理
/**
* CGLib必须依赖于CGLib的类库,需要满足以下要求
* 1. 实现MethodInterceptor接口,重写intercept()
* 2. 使用Enhancer对象.create()产生代理对象
* 3. 因为是继承机制,不能代理final修饰的类
*
* CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的
* CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类
* CGLIB创建代理对象效率较低,执行效率高
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:52
*/
public class CglibDynamicProxy implements MethodInterceptor {
/**
* 代理的目标对象
*/
private Object object;
public CglibDynamicProxy(Object object) {
this.object = object;
}
public Object proxy() {
Class<?> clazz = object.getClass();
// 生成代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(proxy.getClass().getSuperclass().getName());
beforeMethod(object);
Object result = methodProxy.invokeSuper(proxy, objects);
afterMethod(object);
return result;
}
private void beforeMethod(Object object) {
if (object instanceof MusicMediaImpl) {
System.out.println("MusicMediaImpl Start");
} else if (object instanceof MovieMedia) {
System.out.println("MovieMedia Start");
} else {
throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
}
}
private void afterMethod(Object object) {
if (object instanceof MusicMediaImpl) {
System.out.println("MusicMediaImpl End");
} else if (object instanceof MovieMedia) {
System.out.println("MovieMedia End");
} else {
throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
}
}
}
- 验证一
/**
* 相比于静态代理,动态代理在创建代理对象上更加的灵活
*
* 验证一,音乐媒体类实现接口都可以成功代理
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:55
*/
public class Main {
public static void main(String[] args) {
Media musicMedia = new MusicMediaImpl();
// musicMedia.play("音乐");
// 使用Jdk动态代理接口实现方式
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(musicMedia);
Media jdkDynamicProxyMedia = (Media) jdkDynamicProxy.proxy();
jdkDynamicProxyMedia.play("Jdk音乐");
System.out.println("----------");
// 使用Cglib动态代理接口实现方式
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(musicMedia);
Media cglibDynamicProxyMedia = (Media) cglibDynamicProxy.proxy();
cglibDynamicProxyMedia.play("Cglib音乐");
}
}
MusicMediaImpl Start
Play Music: Jdk音乐
MusicMediaImpl End
----------
com.design.proxy.MusicMediaImpl
MusicMediaImpl Start
Play Music: Cglib音乐
MusicMediaImpl End
- 验证二
/**
* 相比于静态代理,动态代理在创建代理对象上更加的灵活
*
* 验证二,电影媒体类未实现接口只有Cglib可以代理
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/31 16:55
*/
public class Main2 {
public static void main(String[] args) {
MovieMedia movieMedia = new MovieMedia();
// movieMedia.play("电影");
// 使用Cglib动态代理Class类
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(movieMedia);
MovieMedia cglibDynamicProxyMedia = (MovieMedia) cglibDynamicProxy.proxy();
cglibDynamicProxyMedia.play("Cglib电影");
System.out.println("----------");
// 使用Jdk动态代理Class类,运行失败,Jdk必须实现接口才可以代理
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(movieMedia);
MovieMedia jdkDynamicProxyMedia = (MovieMedia) jdkDynamicProxy.proxy();
jdkDynamicProxyMedia.play("Jdk电影");
}
}
com.design.proxy.MovieMedia
MovieMedia Start
Play Movie: Cglib电影
MovieMedia End
----------
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.design.proxy.MovieMedia
at com.design.proxy.v2.Main2.main(Main2.java:27)
# 2.3. 总结
- 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道
- JDK 代理只能对实现了接口的类进行代理,而 CgLib 代理可以对普通类进行代理;
- JDK 代理是通过反射的方式来实现动态代理,而 CgLib 则是通过为目标类生成一个子类的方式来实现动态代理
参考