简介
在RPC技术栈中最主要包括:
- 网络传输(大部分RPC框架采用TCP作为底层网络传输协议)。
- 传输协议(
grpc
使用的是HTTP2
,dubbo
使用自定义的dubbo
协议,也可以是Restful
协议和HTTP协议,还可以是hessian
协议)。
- 数据编码(RPC中数据编解码也是重要一环,可以使用像,java自带序列化或者是第三方的,比如:
protobuf
、json
、hessian
等)。
- 动态代理(动态代理在RPC中的作用是在客户的生成服务接口的代理类,让客户的调用服务端接口像是在操作本地接口一样方便,隐藏底层的编解码和网络传输等)。
具备以上四点一个RPC底层就实现完了,为什么说是底层呢,因为一个完整的RPC还要包括服务注册与发现、服务治理、负载均衡、熔断和限流等高级功能。
动态代理
动态代理在RPC当中起到隐藏底层实现细节,让客户端调用服务端接口方法像是在操作本地接口一样方便。说道动态代理我们就来看一下几种动态代理。
JDK动态代理
JDK动态代理是针对接口和实现类的代理,也就是JDK的动态代理只能应用于接口对接口实施代理,代码如下:
public interface IHello { void say(String s); }
|
public class RealHello implements IHello {
@Override public void say(String s) { System.out.println("hello " + s); } }
|
public class HelloDelegate implements InvocationHandler {
private IHello target;
public HelloDelegate(IHello target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用前。。。。。"); Object invoke = method.invoke(target, args); System.out.println("调用后。。。。。"); return invoke; } }
|
public class DynamicProxy { public static void main(String[] args) { IHello iHello = enhanceHello(new RealHello()); System.out.println("代理类名称:" + iHello.getClass().getName()); System.out.println("代理类实现的接口 " + iHello.getClass().getInterfaces()[0].getName()); iHello.say("张三"); }
public static IHello enhanceHello(IHello target) { return (IHello) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(),new Class<?>[] {IHello.class},new HelloDelegate(target)); } }
|
javassist动态代理
Javaassist
就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。Javassist
的定位是能够操纵底层字节码,所以使用起来并不简单,要生成动态代理类恐怕是有点复杂了。但好的方面是,通过Javassist
生成字节码,不需要通过反射完成方法调用,所以性能肯定是更胜一筹的。在使用中,我们要注意一个问题,通过Javassist
生成一个代理类后,此CtClass
对象会被冻结起来,不允许再修改;否则,再次生成时会报错。javassist
比JDK
的动态代理要求要宽泛一下,接口不是必须的。
public class RealHello { public void say(String s) { System.out.println("javassist: " + s); } }
|
public class HelloDelegate<T> implements MethodHandler {
private T target;
public HelloDelegate(T target) { this.target = target; }
@Override public Object invoke(Object o, Method method, Method proceed, Object[] objects) throws Throwable { System.out.println("增强前。。。。。"); Object result = method.invoke(target, objects); System.out.println("增强后。。。。。"); return result; } }
|
public class DynamicProxy { public static void main(String[] args) { RealHello hello = enhanceHello(new RealHello()); hello.say("world"); }
public static <T> T enhanceHello(T target) { ProxyFactory proxy = new ProxyFactory(); proxy.setSuperclass(RealHello.class); try { HelloDelegate<T> delegate = new HelloDelegate<>(target); return (T)proxy.create(new Class<?>[0],new Object[0],delegate); } catch (Exception e) { e.printStackTrace(); } return null; } }
|
bytebuddy动态代理
Byte Buddy
是一个JVM的运行时代码生成器,你可以利用它创建任何类,且不像JDK
动态代理那样强制实现一个接口。Byte Buddy
还提供了简单的API
,便于手工、通过Java Agent
,或者在构建期间修改字节码。
Java
反射API
可以做很多和字节码生成器类似的工作,但是它具有以下缺点:
- 相比硬编码的方法调用,使用 反射
API
非常慢
- 反射
API
能绕过类型安全检查
比起JDK动态代理、cglib
、Javassist
,Byte Buddy
在性能上具有优势。
public class Source { public String hello(String name) { return null; } }
|
public class Target { public static String hello(String name) { return "Hello " + name + "!"; } }
|
public class ObjectProxy3 { public void target() throws IllegalAccessException, InstantiationException { String hello = new ByteBuddy() .subclass(Source.class) .method(ElementMatchers.named("hello")) .intercept(MethodDelegation.to(Target.class)) .make() .load(this.getClass().getClassLoader()) .getLoaded() .newInstance() .hello("World1111"); System.out.println("result: " + hello); } public static void main(String[] args) { try { new ObjectProxy3().target(); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } } }
|
总结
这篇学习了三种动态代理方法,都各自有他们的优缺点。这些只是动态代理中基础。下一篇我们讲解动态代理在RPC中的应用,使用JDK动态代理实现一个最简单的RPC