Skip to content

Commit c19668c

Browse files
committed
rpc and dynamic proxy
1 parent 119abc6 commit c19668c

3 files changed

+273
-65
lines changed

_posts/2020-08-02-java-reflection-dynamic-proxy.md

+43-35
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[toc]
2+
13
---
24
layout: post
35
title: "Java反射与动态代理"
@@ -6,7 +8,7 @@ categories: java proxy reflection
68
tags: java proxy reflection
79
---
810

9-
Java在运行时,能够实时获取对象的类型信息:RTTI(RealTime Type Identification,实时类型识别)。比如多态,父类引用引用子类对象,在调用方法时也能准确地调用子类的override方法。本文从RTTI谈到反射,再聊一聊反射相关的一大应用——动态代理。
11+
Java在运行时,能够实时获取对象的类型信息:RTTI(RealTime Type Identification,实时类型识别)。比如多态,父类引用指向子类对象,在调用方法时能准确地调用子类的override方法。本文从RTTI谈到反射,再聊一聊反射相关的一大应用——动态代理。
1012

1113
1. Table of Contents, ordered
1214
{:toc}
@@ -22,15 +24,15 @@ Java想知道对象的类型信息,肯定得将类型信息存储在某个地
2224

2325
## `getClass()`
2426
每个对象都有一个指向该类的Class对象的指针,反映在代码层面,就是在Object对象里,有获取该Class对象的方法:
25-
```
27+
```java
2628
public final native Class<?> getClass();
2729
```
2830

2931
> 实际上Java将Class类设计为一个泛型类`Class<T>`,即T类型的Class对象。Object的getClass方法返回的是`Class<?>`,意为“任意类型的Class对象”。`?`表示任何事物,所以`Class<?>`等价于`Class`,但是**使用`Class<?>`的好处是可以明确说明你不是因为疏忽而使用了非具体的类引用,你就是选择了非具体的版本**。对于Object超类来讲,它是任意类型的父类,可能返回任意类型的Class对象,所以使用`Class<?>`作为返回值。
3032
3133
## `Class.forName(String)`
3234
在Java中获取Class对象的另一种方法就是使用Class类提供的静态方法:
33-
```
35+
```java
3436
public static Class<?> forName(String className) throws ClassNotFoundException
3537
```
3638
只要给出完整的类名(带包名的类名),就可以获取该类的Class对象。
@@ -53,7 +55,7 @@ Field、Method、Annotation、Constructor等类,就是以上数据结构的实
5355

5456
## 静态代理
5557
假设有一个接口Coder:
56-
```
58+
```java
5759
public interface Coder {
5860

5961
/**
@@ -75,8 +77,8 @@ public interface Coder {
7577
String comment();
7678
}
7779
```
78-
这个接口是程序猿要做的事情。现在有一个Java程序猿:
79-
```
80+
这个接口代表一名程序猿要做的事情。现在有一个Java程序猿:
81+
```java
8082
import lombok.AllArgsConstructor;
8183
import org.apache.commons.lang3.RandomUtils;
8284

@@ -114,7 +116,7 @@ public class JavaCoder implements Coder {
114116
它实现了程序猿接口所规定的任务。
115117

116118
静态代理:
117-
```
119+
```java
118120
/**
119121
* @author liuhaibo on 2018/04/18
120122
*/
@@ -149,13 +151,13 @@ public class StaticProxy implements Coder {
149151
}
150152
```
151153
这个静态代理和程序猿有相同的接口,外部给需求的时候,由它来搞定,但实际上,它只是调用Java程序猿来做事情,这是个假程序员,只是一个代理。当然这个代理也做了一些额外的事情,比如外部需求是“illegal”时,由代理直接拒绝该需求,可以避免程序猿的一些琐事:
152-
```
153-
Coder coder = new JavaCoder("Tom");
154-
Coder staticProxy = new StaticProxy(coder);
154+
```java
155+
Coder coder = new JavaCoder("Tom");
156+
Coder staticProxy = new StaticProxy(coder);
155157

156-
staticProxy.estimateTime("Hello, world");
157-
staticProxy.implementDemands("Send an ad");
158-
staticProxy.implementDemands("illegal");
158+
staticProxy.estimateTime("Hello, world");
159+
staticProxy.implementDemands("Send an ad");
160+
staticProxy.implementDemands("illegal");
159161
```
160162
输出:
161163
```
@@ -175,10 +177,10 @@ No! ILLEGAL demand!
175177

176178
如果要在函数前后做相同的逻辑呢?比如在每一个方法执行前后,都输出一下当前时间。那就需要函数的函数g(x):输入f(x),输出g(f(x))。
177179

178-
**动态代理就是g(x),我们要做的就是写出g(x)的逻辑。而g(x)的入参是一个函数**
180+
**动态代理就是g(x),我们要做的就是写出g(x)的逻辑。g(x)的入参是一个函数f(x)**
179181

180-
Java规定,每一个gx都要实现`InvocationHandler`接口:
181-
```
182+
Java规定,每一个g(x)都要实现`InvocationHandler`接口:
183+
```java
182184
public interface InvocationHandler {
183185

184186
public Object invoke(Object proxy, Method method, Object[] args)
@@ -187,8 +189,8 @@ public interface InvocationHandler {
187189
```
188190
而这个接口的入参正是一个函数:
189191
- **`Method`就是那个函数f(x)**
190-
- **`Object[]`是它的入参的值,也就是f(x)的x的值。为了调用这个函数,得知道它的入参是啥**
191-
- object proxy是生成的动态代理类。把它作为入参纯粹为了多给出一些信息,其实一般情况下用不到它。
192+
- **`Object[]`是它的入参的值,也就是f(x)的x的值。为了调用这个函数,得知道它的入参是什么**
193+
- object proxy是生成的动态代理对象。把它作为入参是为了多给出一些信息,其实一般情况下用不到它。
192194

193195
**但是java调用一个method需要有对象才行,所以动态代理少传入了一个原始object,也就是被代理的object**
194196
因此一般实现InvocationHandler(或者说g(x))的时候,都传入一个被代理的object作为构造函数的参数。
@@ -197,10 +199,10 @@ public interface InvocationHandler {
197199

198200
Java创建动态代理的流程:
199201
1. 开发者写一个g(x),它必须是InvocationHandler的实现类;
200-
2. 开发者指定一个接口,这个接口的所有方法都会被应用g(x);
202+
2. 开发者指定一个接口,**这个接口的所有方法f(x)都会被应用g(x),从而变成g(f(x))**
201203
2. jvm根据指定的接口,自己动态生成一个类,实现该接口;
202-
2. 接口里每一个方法都是这么实现的:每一个方法都调用g(x);
203-
3. g(x)的返回值,将作为该动态代理对象的相应方法的返回值
204+
2. 接口里每一个方法都是这么实现的:g(f(x))。即每一个f(x)方法都调用g(x);
205+
3. g(x)的返回值,将作为该动态代理对象的相应方法g(f(x))的返回值
204206

205207
所以理解Java动态代理,还要区分好Java会做什么,开发者要做什么。开发者做好自己的事情就好了,其他的都会由Java按照契约搞定。
206208

@@ -218,7 +220,7 @@ InvocationHandler就是java和开发者之间最核心的契约:
218220
2. **且该handler的返回值,将作为动态代理方法的返回值**
219221

220222
比如下面的InvocationHandler实现,在方法调用前后都会输出当前时间:
221-
```
223+
```java
222224
import lombok.AllArgsConstructor;
223225

224226
import java.lang.reflect.InvocationHandler;
@@ -268,26 +270,30 @@ public class BibiAroundInvocationHandler implements InvocationHandler {
268270
3. 设置invoke函数的返回值。首先要知道BibiAroundInvocationHandler#invoke的返回值会被Java用作动态代理类的方法调用后的返回值。这里我们只想在方法调用前后bibi一下,无意改变被代理对象方法的返回值,所以对于被代理对象,我们应该将它的返回值作为BibiAroundInvocationHandler#invoke的返回值。不过最后返回前我还是给返回值加上了一个“(Proxy) ”前缀,作为一个demo,我们就能更明显地看到,动态代理对象的返回值被我们刻意改变了,比被代理对象的返回值多加了个前缀。
269271

270272
### Proxy
271-
解决了InvocationHandler实现的核心问题,另一个简单的问题还没解决:为哪个接口生成动态代理类。
273+
解决了InvocationHandler实现的核心问题,再看第一个简单的问题:为哪个接口生成动态代理类。
272274

273275
Java的Proxy类提供了创建动态代理类的方法,有两个方法比较重要:
274276

275-
- `public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)`:用于生成给定接口的动态代理类。需要传入接口和该接口的ClassLoader,ClassLoader一般直接传入接口的ClassLoader即可;
276-
- `public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException`用于生成给定借口的动态代理对象。其实这个方法包含了上一个方法,毕竟想生成对象,首先得生成代理类,然后再new一个对象。所以该方法大致有三步:
277+
- **获取代理类**`public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)`:用于生成给定接口的动态代理类。需要传入接口和该接口的ClassLoader,ClassLoader一般直接传入接口的ClassLoader即可;
278+
- **获取代理对象**`public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException`用于生成给定接口的动态代理对象。其实这个方法包含了上一个方法,毕竟想生成对象,首先得生成代理类,然后再new一个对象。所以该方法大致有三步:
277279
+ 生成动态代理类:`Class<?> cl = getProxyClass0(loader, intfs)`
278280
+ 获取动态代理类的构造函数:`private static final Class<?>[] constructorParams = { InvocationHandler.class }; final Constructor<?> cons = cl.getConstructor(constructorParams)`
279281
+ 生成动态代理对象:`cons.newInstance(new Object[]{h})`
280282

281-
值得关注的是生成对象的那一步。首先使用反射获取生成的动态代理类的构造函数,该构造函数的参数是InvocationHandler。然后给构造函数传入开发者定义好的InvocationHandler实例,生成动态代理对象。所以我们可以推测,**Java在生成动态代理类的时候,除了要实现接口中给定的方法,一定也添加了一个参数是InvocationHandler的构造方法**
282-
283283
> 由动态代理的方法只能传入interface参数可知,Java的动态代理**只能为接口生成代理,不能为具体类生成代理**
284284
285+
**所以`newProxyInstance`是个很重要的方法,java中使用它生成代理对象。**
286+
285287
生成动态代理类并执行方法的代码:
286-
```
288+
```java
287289
InvocationHandler handler = new BibiAroundInvocationHandler(coder);
288290

289291
// 直接生成一个代理对象实例(实际上也是两步:先在jvm中生成一个代理类Class的对象,再使用该Class对象生成代理对象)
290-
Coder proxy = (Coder) Proxy.newProxyInstance(coder.getClass().getClassLoader(), coder.getClass().getInterfaces(), handler);
292+
Coder proxy = (Coder) Proxy.newProxyInstance(
293+
coder.getClass().getClassLoader(),
294+
coder.getClass().getInterfaces(),
295+
handler
296+
);
291297

292298
proxy.estimateTime("Hello, world");
293299
proxy.implementDemands("Send an ad");
@@ -312,7 +318,7 @@ Tom: I use Java to finish Send an ad
312318
上面分析了Java动态代理架构设计上,Java和开发者之间的契约。现在来看看,Java生成的代理类究竟长什么样。
313319

314320
首先,使用java提供的ProxyGenerator类,生成一个Coder接口的动态代理类,名为DynamicCoder。然后把这个类的字节码写到一个文件里:
315-
```
321+
```java
316322
/**
317323
* @author liuhaibo on 2018/04/19
318324
*/
@@ -329,7 +335,7 @@ public class ProxyUtil {
329335
}
330336
```
331337
然后使用IDE反编译字节码,大致看看生成的动态代理类的代码长啥样:
332-
```
338+
```java
333339
import example.proxy.Coder;
334340
import java.lang.reflect.InvocationHandler;
335341
import java.lang.reflect.Method;
@@ -431,11 +437,13 @@ public final class DynamicCoder extends Proxy implements Coder {
431437

432438
由此,我们亲眼看到了Java的确是按照契约,在jvm运行时,为我们生成了一个动态代理类。
433439

440+
> Java在生成动态代理类的时候,除了要实现接口中给定的方法,还额外添加了一个参数是`InvocationHandler`**构造方法**。在上面获取代理对象的`newProxyInstance`方法里可以看到,实现的时候首先使用反射获取了这个参数为`InvocationHandler`的动态代理类的构造函数,然后给构造函数传入开发者定义好的`InvocationHandler`实例,生成动态代理对象。
441+
434442
## 正向梳理
435443
**现在,让我们正向理一理动态代理调用的全过程**
436444
1. 当使用动态代理类,调用`proxy.estimateTime("Hello, world")`时;
437445
2. 动态代理类调用它的方法:
438-
```
446+
```java
439447
public final void estimateTime(String var1) {
440448
try {
441449
super.h.invoke(this, m5, new Object[]{var1});
@@ -449,7 +457,7 @@ public final class DynamicCoder extends Proxy implements Coder {
449457
3. 此时`var1`是hello world字符串,然后执行`super.h.invoke(this, m5, new Object[]{var1})`;
450458
4. `h`是我们的handler:`BibiAroundInvocationHandler`;
451459
5. 所以调用`h.invoke`,就是在调用:
452-
```
460+
```java
453461
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
454462

455463
System.out.println(">>> Before: " + LocalDateTime.now());
@@ -477,7 +485,7 @@ cglib和jdk的动态代理都是在运行时生成代理类,原理上是一致
477485

478486
本质上,我们用cglib写的g(x)和用jdk proxy写g(x)的写法一样,也是在调用被代理类的方法。**但是,cglib是给代理类生成了子类,所以在写g(x)的时候, cglib不需要我们自己传入被代理类(jdk的动态代理需要自己传入被代理对象,调用它的方法),直接调用`super.<method>`就行了**!它接口方法签名里的MethodProxy,拥有invokeSuper方法,可以直接调用“生成的代理类的super class”的方法, 也就是“被代理类”的方法。这也说明了,cglib生成的代理类,都是被代理类的子类。
479487

480-
```
488+
```java
481489
/**
482490
* @author puppylpg on 2022/07/03
483491
*/
@@ -495,7 +503,7 @@ public class CglibBibiAroundInterceptor implements MethodInterceptor {
495503
```
496504

497505
生成代理对象也非常直白:
498-
```
506+
```java
499507
Enhancer enhancer = new Enhancer();
500508
// cglib 不需要传入被代理的类,只需要给出要代理的类的class文件,它自己会创建
501509
enhancer.setSuperclass(JavaCoder.class);

_posts/2023-02-26-rpc.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[toc]
2+
13
---
24
layout: post
35
title: "RPC"
@@ -123,7 +125,7 @@ linux进程间通信(Inter-Process Communication,IPC)常用的方法:
123125
> 还有[一些很好的示意图](https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247485318&idx=1&sn=0da0a684639106f548e9d4454fd49904&scene=21#wechat_redirect)
124126
125127
`ipcs`命令可以查看linux系统的semaphore/message queue/shared memory使用情况:
126-
```
128+
```bash
127129
Ξ ~ → ipcs -a
128130

129131
------ Message Queues --------
@@ -161,6 +163,10 @@ semaphore max value = 32767
161163

162164
所以:rpc是一种高层次/语言层次的特征,ipc是一种低层次/系统层次的特征,二者不是一个层面的东西。**两个不同领域的东西是不存在包含关系的,所以说rpc不是一种ipc!**
163165

166+
> 这一观点和[既然有 HTTP 请求,为什么还要用 RPC 调用?](https://www.zhihu.com/question/41609070/answer/1030913797)是类似的,其认为restful不是rpc的一种,除非以非常宽泛的概念理解rpc。
167+
>
168+
> 同样,如果够宽泛,甚至也可以认为restful是一种ipc。这种在概念上的宽泛的理解意义不大,还是把他们看成并列的方案,更能看出他们的区别。
169+
164170
## RPC现状
165171
rpc虽然模仿ipc未遂(“像调用本地方法一样”),性能上做不到,但逻辑上还是模仿得来的——只要能搞定这三个问题:
166172
1. 数据表示(序列化,eg:protobuf);
@@ -203,7 +209,7 @@ rpc虽然模仿ipc未遂(“像调用本地方法一样”),性能上做
203209
2. and two structured types (`Objects` and `Arrays`)
204210

205211
比如批量request:
206-
```
212+
```json
207213
[
208214
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
209215
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
@@ -214,7 +220,7 @@ rpc虽然模仿ipc未遂(“像调用本地方法一样”),性能上做
214220
]
215221
```
216222
批量response:
217-
```
223+
```json
218224
[
219225
{"jsonrpc": "2.0", "result": 7, "id": "1"},
220226
{"jsonrpc": "2.0", "result": 19, "id": "2"},
@@ -234,7 +240,7 @@ json-rpc只规定了使用json进行数据表示,并没有限定具体传输
234240
所以如果要支持多种协议,实现的代码也不少,比如:[jsonrpc4j](https://github.com/briandilley/jsonrpc4j)
235241

236242
# 感想
237-
什么感觉?类似于我是地球人,今天才知道这是一个三体的世界。
243+
什么感觉?就好像我是地球人,但今天才知道这竟然是一个三体的世界!
238244

239245
rpc的本质、换个思路看rpc就能引出另一种方案rest。我灌顶了。
240246

0 commit comments

Comments
 (0)