设计模式之代理模式(文末赠书)
静态代理
在开始代理模式定义之前我们先看一段常见的业务逻辑,假设你有个接口ISubject,接口有个operator方法,然后有个具体的实现类来实现此方法:
- 接口类
public interface ISubject {
void operator();
}
- 具体实现类
public class RealSubject implements ISubject{
@Override
public void operator() {
System.out.println("do something");
}
}
那现在你有个新的需求,就是在执行operator方法之前需要打印一段日志,但是不能修改原RealSubject的业务逻辑,那该怎么实现呢?
这时候大家伙肯定会想到建一个新的实现类来实现ISubject,并将RealSubject组合进来,真正的业务逻辑还是调用RealSubject的operator方法来实现,在执行operator之前实现我们需要的业务逻辑,比如日志打印。
public class SubjectProxy implements ISubject{
private RealSubject subject;
public SubjectProxy(RealSubject subject) {
this.subject = subject;
}
@Override
public void operator() {
System.out.println("this is log");
subject.operator();
}
}
客户端调用的时候我们直接使用SubjectProxy来实现业务逻辑即可:
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ISubject proxy = new SubjectProxy(realSubject);
proxy.operator();
}
}
执行结果如下:
看到这里你可能会想,这就是代理模式?就这?
是的,这就是代理模式,理解了这个例子你就差不多掌握了代理模式,只不过目前还是静态代理,等会我们再讲讲如何实现动态代理。
让我们先来看看代理模式的定义。
代理模式定义
代理模式的类图结构如下:
图中的 Subject 是程序中的业务逻辑接口,RealSubject 是实现了 Subject 接口的真正业务类,Proxy 是实现了 Subject 接口的代理类,封装了一个 RealSubject 引用。「在程序中不会直接调用 RealSubject 对象的方法,而是使用 Proxy 对象实现相关功能。」
Proxy.operator()
方法的实现会调用其中封装的 RealSubject 对象的 operator() 方法,执行真正的业务逻辑。
这就是 “代理模式”。
通过上面的例子我们可以看出,「使用代理模式可以在不修改被代理对象的基础上,通过扩展代理类来进行一些功能的附加与增强,值得注意的是,代理类和被代理类应该共同实现一个接口,或者共同继承某个类。」
动态代理
上面例子中我们展示的是代理模式中的 “静态代理模式”,这是因为我们需要预先定义好代理类SubjectProxy,当需要代理的类很多时,就会出现很多的Proxy类。
在这种场景下我们就需要使用动态代理了,动态代理的实现方式又有很多种,本章我们来看看JDK原生的动态代理。
JDK动态代理的代码实现:
- 动态代理类
public class SubjectInvokerHandler implements InvocationHandler {
//真正的业务对象
private Object target;
public SubjectInvokerHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行前置业务逻辑");
//真正的业务逻辑
method.invoke(target, args);
System.out.println("执行后置业务逻辑");
return null;
}
public Object getProxy() {
//创建代理对象
return Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(),
this
);
}
}
动态代理的核心就是InvocationHandler 接口,它只有一个invoke()方法,这个方法决定了如何处理传递过来的方法调用。
- 客户端:
public class Client {
public static void main(String[] args) {
ISubject realSubject = new RealSubject();
SubjectInvokerHandler invokerHandler = new SubjectInvokerHandler(realSubject);
//获取代理对象
ISubject proxy = (ISubject) invokerHandler.getProxy();
proxy.operator();
}
}
对于需要相同代理逻辑的业务类,只需要提供一个 InvocationHandler 接口实现类即可。在 Java 运行的过程中,JDK会为每个 RealSubject 类动态生成相应的代理类并加载到 JVM 中,然后创建对应的代理实例对象,返回给上层调用者。
- 执行效果:
可以看到我们这里并没有像静态代理一样实现具体的代理类,但是最终却实现了同样的效果。
JDK动态代理实现原理
了解了动态代理的基本使用后我们来看看动态代理的实现原理。
创建动态代理的入口类是Proxy.newProxyInstance()
这个静态方法,它的三个参数分别是加载动态生成的代理类的类加载器、业务类实现的接口和InvocationHandler对象。(代码有点长,我们截取一下):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
//获取代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
...
//获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
return cons.newInstance(new Object[]{h});
...
}
...
}
newProxyInstance()
最终返回一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 则由 getProxyClass0()
方法获取,代码如下:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
...
// 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;
// 否则通过ProxyClassFactory创建实现指定接口的代理类
return proxyClassCache.get(loader, interfaces);
}
proxyClassCache 是定义在 Proxy 类中的静态字段,主要用于缓存已经创建过的代理类,定义如下:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache.get()
方法会首先尝试从缓存中查找代理类,如果查找不到,则会创建 Factory 对象并调用其 get() 方法获取代理类。Factory 是 WeakCache 中的内部类,Factory.get() 方法会调用 ProxyClassFactory.apply()
方法创建并加载代理类。
ProxyClassFactory.apply()
方法首先会检测代理类需要实现的接口集合,然后确定代理类的名称,之后创建代理类并将其写入文件中,最后加载代理类,返回对应的 Class 对象用于后续的实例化代理类对象。该类的核心代码如下:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
// 代理类的前缀是 $Proxy
private static final String proxyClassNamePrefix = "$Proxy";
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
long num = nextUniqueNumber.getAndIncrement();
//代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* 生成代理类,并写入文件
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
...
}
}
}
小结
JDK动态代理的实现原理是动态创建代理类并通过指定类加载器进行加载,在创建代理对象时将InvocationHandler对象作为构造参数传入。当调用代理对象时,会调用 InvocationHandler.invoke()
方法,从而执行代理逻辑,并最终调用真正业务对象的相应方法。
总结
本章内容主要是讲设计模式中的代理模式,代理模式的作用就是在不修改被代理对象的源码上,进行功能增强。这种开发模式在AOP面向切面编程领域很常见。
代理模式中静态代理需要自己编写代理类,动态代理中代理类通过Proxy.newInstance()
方法生成,他们的实质都是面向接口编程。
那么如果不是接口又想实现代理怎么办呢?咱们下期见。不对,咱们还要先送书!
赠书
《JAVA 核心编程》 柳伟卫著
在此感谢清华大学出版社的赞助
赠书方式
简单粗暴,本文留言,留言内容点赞数最高者获得一本。
时间截止10月15日的20点整。
如果本文对你有帮助,
别忘记来个三连:
点赞,转发,评论
。
咱们下期见!
收藏 等于白嫖,点赞 才是真情!
End
- 看到那个Edward 了吗?对!其实它是个Python库
- (35) 泛型 (上) - 基本概念和原理 / 计算机程序的思维逻辑
- 将linux下的rm命令改造成mv到指定的目录下
- (36) 泛型 (中) - 解析通配符 / 计算机程序的思维逻辑
- Python优化第一步: 性能分析实践
- Python云计算框架:OpenStack源码分析之RabbitMQ(二)
- 编程获得CPU的主频
- Python多进程并行编程实践:以multiprocessing模块为例
- 由函数clock想到的
- (34) 随机 / 计算机程序的思维逻辑
- 创建和使用Windows静态链接库
- (37) 泛型 (下) - 细节和局限性 / 计算机程序的思维逻辑
- 快速失败Vs安全失败(Java迭代器附示例)
- Flask-SocketIO 文档译文
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android 中RecyclerView顶部刷新实现详解
- Android开发实现图片平移、缩放、倒影及旋转功能的方法
- Grafana + InfluxDB 实现 Jmeter 压测的图形化监控
- Android 浮动编辑框的具体实现代码
- Android实现Path平滑的涂鸦效果实例
- Android CameraManager类详解
- Android开发实现自定义水平滚动的容器示例
- Android Studio开发之 JNI 篇的简单示例
- Android自定义View播放Gif动画的示例
- Android下拉框PopupWindow使用详解
- Android实现自动文本框提示功能
- Android开发实现拍照功能的方法实例解析
- Android开发实现模仿360二维码扫描功能实例详解
- Android中控制和禁止ScrollView自动滑动到底部的方法
- ImageView 实现Android colorPikcer 选择器的示例代码