引子
今天在学netty相关的内容的时候,知道了netty是RPC(远程过程调用)的一种实现,就想深入了解一下RPC的相关只是,在看RPC的时候发现RPC框架又是基于代理模式实现的,为此兜兜转转就到了今天要讨论的内容,代理模式。找了相关的资料后,感觉有篇文章写得挺好,这里分享一下:https://segmentfault.com/a/1190000007089902。
基本概念
代理模式由上图可见,代理模式主要涉及下面几个部分:
- 代理类 Proxy
- 实际处理类 RealSubject
- 代理类和实际处理类共同的接口 Subject
- 方法调用方 Client
通过引入代理类,防止调用方直接调用实际处理类的方法,从而实现解耦。
下面就代理模式分为静态代理和动态代理两个方面来讨论。后面想学习的RPC远程调用就是基于代理模式实现的。
静态代理
首先举一个例子, 假设我们需要实现一个从不同存储介质(例如磁盘, 网络, 数据库)加载图片的功能, 那么使用静态代理的方式的话, 需要实现如下工作:
- 定义一个加载图片的接口
- 实现实际操作对象(LoadFromDisk, LoadFromNet, LoadFromDB)
- 实现代理对象
首先定义加载图片的接口:
public interface LoadImage {
Image loadImage(String name);
}
接着编写代理类:
public class LoadImageProxy implements LoadImage {
private LoadImage loadImageReal;
public LoadImageProxy(LoadImage loadImageReal) {
this.loadImageReal = loadImageReal;
}
@Override
public Image loadImage(String name) {
return loadImageReal.loadImage(name);
}
}
代理类的构造方法引入实际处理类的实例,在复写的loadImage中执行实际处理类的具体方法。所以调用方调用如下:
public class App {
public static void main(String[] args) {
LoadFromDisk loadFromDisk = new LoadFromDisk();
LoadImageProxy proxy = new LoadImageProxy(loadFromDisk);
proxy.loadImage("/tmp/test.png");
}
}
根据代理模式, 我们在上面的代码中展示了一个基本的静态代理的例子, LoadImageProxy 是代理类, 它会将所有的接口调用都转发给实际的对象, 并从实际对象中获取结果. 因此我们在实例化 LoadImageProxy 时, 提供不同的实际对象时, 就可以实现从不同的介质中读取图片的功能了。
动态代理
所谓动态代理就是动态地创建代理并且动态地处理所代理对象的方法调用。
在 Java 的动态代理中, 涉及两个重要的类或接口:
- Proxy
- InvocationHandler
下面就先看下这两个类的内容。
关于 Proxy 类
Proxy 主要是提供了 Proxy.newProxyInstance 静态方法, 其签名如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数如下:
- loader: 即类加载器, 指定由哪个ClassLoader对象来对生成的代理对象进行加载
- interfaces: 一个Interface对象的数组, 表示的是代理对象所需要实现的接口
- h: 即 InvocationHandler 的实现对象. 当调用代理对象的接口时, 实际上会 通过 InvocationHandler.invkoe 将调用转发给实际的对象.
关于 InvocationHandler 接口
我们在前面提到过, 在调用 Proxy.newProxyInstance 方法时, 需要传递一个 InvocationHandler 接口的实现对象, 那么这个 InvocationHandler 接口有什么用呢?
实际上, 在 Java 动态代理中, 我们都必须要实现这个接口, 它是沟通了代理对象和实际对象的桥梁, 当我们调用了代理对象所提供的接口方法时, 此方法调用会被封装并且转发到 InvocationHandler.invoke 方法中, 在 invoke 方法中调用实际的对象的对应方法。
InvocationHandler.invoke 方法的声明如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这个方法接收三个参数:
- proxy: 指代理对象, 即 Proxy.newProxyInstance 所返回的对象(注意, proxy 并不是实际的被代理对象)
- method: 我们所要调用真实对象的方法的 Method 对象
- args: 调用真实对象某个方法时接受的参数
动态代理例子
使用动态代理的步骤很简单, 可以概括为如下两步:
- 实现 InvocationHandler 接口, 并在 invoke 中调用真实对象的对应方法
-
通过 Proxy.newProxyInstance 静态方法获取一个代理对象.
下面就动态代理再举个例子:
timg.jpg
首先还是用之前加载图片的接口:
public interface LoadImage {
Image loadImage(String name);
}
接着实现InvocationHandler接口:
public class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(proxied, args);
}
}
可以看到, 在实现的 invoke 方法中, 我们简单地通过 method.invoke(proxied, args) 来调用了真实对象的方法.
有了 InvocationHandler 后, 我们就可以创建代理对象并通过代理对象来操作实际对象了:
public class App {
public static void main(String[] args) {
// 实际对象
LoadFromDisk loadFromDisk = new LoadFromDisk();
// 通过 Proxy.newProxyInstance 静态方法创建代理对象
LoadImage loadImage = (LoadImage) Proxy.newProxyInstance(LoadImage.class.getClassLoader(), new Class[]{LoadImage.class}, new DynamicProxyHandler(loadFromDisk));
// 通过代理对象操作实际对象.
loadImage.loadImage("/tmp/test.png");
}
}
上述代码中,当我们执行了代理类实例的loadImage方法时,会转而执行实际处理类LoadFromDisk的具体方法。而且,如果实际处理类有很多方法,通过执行代理类的方法的时候,都先会通过执行DynamicProxyHandler 这个类的invoke方法再去调用实际处理类的具体方法。所以比如如果想在实际处理类的每个方法执行前后都加个打印日志就只要在InvocationHandler的invoke方法中加个日志就行了,而不必在实际处理类的每个方法内部都加上打印,这就是动态代理相比于静态代理的优势。
网友评论