上一篇我们跟踪了RMI服务端(包含注册中心,以下提到的服务端均是这个含义)的源码。知道了服务端是如何管理服务的——通过一个内存数据结构,HashTable,来完成与服务相关的操作(存储、新增、修改、删除、查找):
- 发布服务——调用Registry对象的bind()方法或者rebind()方法
- 删除服务——调用Registry对象的unbind()方法
- 查找服务——调用Registry对象的lookup()方法
- 查询所有服务——调用Registry对象的list()方法
知道了服务端是通过一个Socket对象监听1099端口来接收客户端请求的;也知道了对外提供服务的其实是一个代理对象。Socket、代理,听着这一个个耳熟能详的名字,大家是不是觉得很Easy。不过,我还是想透过这篇文章,加深一下自己的认识。如果下面描述中有不对的地方,欢迎大家评论区留言批评指正,在此我先说声:谢谢!
前一节我们看到了UnicastServerRef#export(Remote, Object, boolean),发现程序会首先获取当前Remote对象的真实类型,比如Registry的实现类RegistryImpl;然后调用Util类的createProxy方法创建一个Remote对象。通过方法名,不难看出其主要目的是创建一个代理对象。进入该方法可以发现其中确实有这样一句代码:
InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef);
不过上一节创建RegistryImpl代理时并没有走到这句,而是走到了createStub(remoteClass, clientRef)这一行,最终创建了一个RegistryImpl_Stub对象。看到这里,我糊涂了:根据方法名可以确定这里要创建代理类,但RegistryImpl_Stub的创建怎么看着不像是创建代理呢?暂且不管这个,先看一下代理的知识吧!
代理,个人理解就是替人代劳,是中间商,享受一定的收益。比如同事请你代劳相亲(为什么我遇不到这种乐于助人的同事),又或者同事请你代劳吃饭(听着怎么这么奇怪)。好了这种玩笑话不说了,假如你是某工厂老板,生产出了一款产品,想将其推向市场,但又觉得这个工作比较繁琐,于是将其委托给了一家实力雄厚的销售公司,由其全权代理该产品的销售工作,这样你可以专注于自己产品的生产与改进。这个时候你和推销公司之间就是一种代理关系。在软件开发领域也有用到这种思想,比如spring中的事务。在我们工作中使用的代理通常分为两大类:
上面说过Util#createProxy()这个方法的主要作用是创建代理对象,而该方法中的代码createStub(remoteClass, clientRef)看着与代理无关,实际上是有关系的,所以上面疑问就是无知惹的祸。先来看一下类结构图:
从图中可以看出RegistryImpl和RegistryImpl_Stub同时实现了Registry接口,同时他们又间接的实现了Remote接口,而RegistryImpl_Stub又直接实现了Remote接口。下面我们对这两个类做一个比较:
RegistryImpl |
RegistryImpl_Stub(Client) |
|
1 |
bind() |
bind() |
2 |
list() |
list() |
3 |
lookup() |
lookup() |
4 |
rebind() |
rebind() |
5 |
unbind() |
unbind() |
7 |
createRegistry(int regPort) |
|
8 |
setup(UnicastServerRef uref) |
|
9 |
checkAccess(String op) |
|
10 |
getID() |
|
11 |
pathToURLs(String path) |
|
12 |
registryFilter(ObjectInputFilter.FilterInfo filterInfo) |
|
13 |
getAccessControlContext(int port) |
|
14 |
getTextResource(String key) |
|
15 |
initRegistryFilter() |
注意:主要关注表中前五行的方法即可。客户端调用服务端的这五个方法时,会首先调用RegistryImpl_Stub类(这个类的对象存在于客户端,它是服务端RegistryImpl对象在客户端的代理)中的对应方法,比如lookup ()(该调用操作会经过一系列处理,然后将请求转发给服务端,由其进行处理并返回保存在服务端的服务对象)。下图RegistryImpl_Stub类中lookup ()的代码:
这里不对其调用细节进行展开,因为这涉及到TCP通信的内容,后续会进行补充。从这里我们可以看出这个RegistryImpl_Stub是一个代理类,其主要为RegistryImpl工作,所以它们之间是一对一的关系。由于这里的代理类要做的工作比较特殊(建立与服务端的连接,获取服务端的服务对象,调用服务端的服务对象),不像事务那样通用,所以这里没有使用jdk或cglib动态代理,而采用了静态代理。(这是个人理解,如果不对,还望指正!)。
这里我们可以总结一下,创建静态代理的过程:
注意:代理类可以直接管理被代理类,也可以不直接管理被代理类。直接管理被代理类方式中,代理类和被代理类一般位于同一jvm进程中,这时的代理类可以直接与被代理类进行通信,也就是可以直接调用被代理类中的相关方法;而在不直接管理方式中,代理类和被代理类一般位于不同的jvm进程中,就比如RegistryImpl和RegistryImpl_Stub,这时代理类无法直接与被代理类进行通信,只能通过间接方式进行通信。
下面我们用一个案例来加深对静态代理的理解。有这样一个需求:现在有一个业务交易在线上稳定运行,但是因为某些原因,业务提出要对所有交易进行验证,如果当前交易的账户处于管控状态,则限制其每次交易金额只能小于等于200块,如果大于200块,直接拒绝并提示用户。接到这个需求我们可以考虑采用这里的静态代理来实现。首先公共接口和目标类都存在,我们只需要创建一个目标类的代理类,比如ConsumeServiceProxy.java,该类直接管理目标类,并实现ConsumeService接口,伪代码如下所示:
// 注意:这里注入的一定是目标类
@Autowired
private ConsumeService consumeService;
public void consume() {
// 进行风险校验,如果校验不同过则直接拒绝交易
// 校验通过,则调用目标类中的某个方法
consume.consume();
}
使用这种方式的好处在有:老代码处理逻辑不动,降低老代码出错概率;使开发者专注于新业务开发。
前面说过动态代理就是编写的代理类不专门为某个类工作,而是为所有目标类工作,此时代理类和目标类之间的关系是一对多的关系。实现动态代理的方式有多种,最常见的两种是jdk动态代理和cglib动态代理。这里我们主要讲jdk动态代理。先回到那段看着跟jdk动态代理很像的代码处:
InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef);
从这段代码可以看出InvocationHandler和RemoteObjectInvocationHandler看着很像,它们有没有关系呢?答案是有的。先看下面这个类图:
由图可知RemoteObjectInvocationHandler类实现了InvocationHandler接口,并实现了其中的invoke()方法。该接口代理的是RemoteRef类型的实现类,比如代码这块的UnicastRef对象。创建完RemoteObjectInvocationHandler对象后,后面有这样一句代码:
Proxy.newProxyInstance(loader, interfaces, handler);
这段代码的主要作用就是创建代理对象,这里的实际类型为Remote。先看一下RMIServiceImpl的类结构图:
图中的RMIServiceImpl和RMIService是自己定义的。它们间接实现了Remote接口。所以这里创建代理的完整代码如下图所示:
创建完Remote代理对象之后,会返回给上层调用者,即UnicastServerRef的exportObject()方法,该方法会Remote类型的impl对象、Remote类型的代理对象等合并创建成一个Target对象,具体代码为:Target target = new Target(impl, this, stub, ref.getObjID(), permanent);,之后再调用UnicastRef对象的export()方法,将该Target对象传递进去。export()之后的代码就不再深入了,我们继续回到创建代理那部分代码。根据这里的用法,我们可以总结一下使用Java动态代理的步骤。具体步骤如下所示:
下面我们还是用一个案例来加深对java动态代理的理解。需求依旧是上面的需求,只不过业务方不再只要求消费交易做个判断,而是要求系统中所有与金钱有关的交易都进行这个判断。这个时候如果按照静态代理写法,就需要添加多个代理类,很显然工作量增加了。如果是我肯定是不愿意的,但是如果大家对代码量要求很多,那多写写也无妨(毕竟人家要求每月提交的代码量必须超过12000行呢)。不过,我不想这样写,因为这种纯粹的体力工作不能带来任何精神上的快感!下面我们就按照动态代理的步骤尝试一下吧!
public class VarifyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 进行业务校验
// 校验通过,则执行被代理对象的方法,并获得返回值
Object result = method.invoke(目标对象, args);
System.out.println("演出后的增强:结算费用并纳税");
return result;
}
}
这里有两个问题要在使用时仔细考虑一下:
通过上面我们了解了java动态代理的开发步骤,那么其原理究竟是上面呢?我们知道上面c步骤创建完代理对象后,调用Object中的方法时,会走VarifyInvocationHandler中的invoke()方法,然后执行前置业务逻辑,接着执行业务逻辑,最后执行后置业务逻辑。那VarifyInvocationHandler和真实的业务处理实现类究竟是怎么关联起来的呢?先看下面的这段代码:
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy implements Actor {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
private static final Method m4;
private static final Method m5;
public $Proxy0(InvocationHandler var1) { super(var1); }
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String dance() {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sing(int var1) {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void rap() {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("dance");
m4 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("sing", Integer.TYPE);
m5 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("rap");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(var0.toString());
}
}
}
根据代码可以知道类中Method类型的m0\m1\m2\m3\m4\m5分别代表的是hashCode()\equals()\toString()\dance()\sing()\rap()。另外类中还有一个InvocationHandler类型的变量h,这个来源于其继承的父类Proxy,jvm在创建$Proxy0对象时,会把我们创建的实现了InvocationHandler接口的VarifyInvocationHandler对象注入其中,以方便后续使用。继续看$Proxy0这个类的代码,其又实现了Actor接口,这接口就是我们自己定义的代理接口(也可以称为被代理接口),之后就实现了代理接口中的所有方法,其中的处理逻辑就是调用实现了InvocationHandler接口的VarifyInvocationHandler对象中的invoke()方法。具体看下面这张图:
从图中可以看出我们上面的理解和代码逻辑一致。那这里的this表示的是$Proxy0类型的当前代理对象。那这个代理对象究竟怎么和最终的业务处理实现类关联起来的呢?看下面这张图(图中代码是前文的代码示例):
图中的目标对象就是代理对象和最终业务对象关联的关键。这个目标对象在我的测试代码中是直接定义在main()方法中的,实战开发中需要考虑如何将这个对象传递进来,可以按照下图这种方式实现: