RPC技术之动态代理(一)

简介

在RPC技术栈中最主要包括:

  1. 网络传输(大部分RPC框架采用TCP作为底层网络传输协议)。
  2. 传输协议grpc使用的是HTTP2dubbo使用自定义的dubbo协议,也可以是Restful协议和HTTP协议,还可以是hessian协议)。
  3. 数据编码(RPC中数据编解码也是重要一环,可以使用像,java自带序列化或者是第三方的,比如:protobufjsonhessian等)。
  4. 动态代理(动态代理在RPC中的作用是在客户的生成服务接口的代理类,让客户的调用服务端接口像是在操作本地接口一样方便,隐藏底层的编解码和网络传输等)。

具备以上四点一个RPC底层就实现完了,为什么说是底层呢,因为一个完整的RPC还要包括服务注册与发现、服务治理、负载均衡、熔断和限流等高级功能。

动态代理

动态代理在RPC当中起到隐藏底层实现细节,让客户端调用服务端接口方法像是在操作本地接口一样方便。说道动态代理我们就来看一下几种动态代理。

JDK动态代理

JDK动态代理是针对接口和实现类的代理,也就是JDK的动态代理只能应用于接口对接口实施代理,代码如下:

  • 接口代码
/**
* 代理的接口类,InvocationHandler和proxy分开,没有添加泛型
*/
public interface IHello {
void say(String s);
}
  • 接口实现类(被代理类)
//被代理类,可以对类进行增强
public class RealHello implements IHello {

@Override
public void say(String s) {
System.out.println("hello " + s);
}
}
  • 代理类
//代理类,必须继承自InvocationHandler接口
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("调用前。。。。。");
//target是被代理的接口实现类,所有需要实现类的对象。如果只是代理,
// 不发起invoke就不需要接口实现对象。RPC就是这样实现,所以client端不需要指定实现类示例。
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对象会被冻结起来,不允许再修改;否则,再次生成时会报错。javassistJDK的动态代理要求要宽泛一下,接口不是必须的。

  • 被代理类
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);
// create方法传递两个空数组
// 分别代表构造器的参数类型数组和构造器的参数实例数组
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可以做很多和字节码生成器类似的工作,但是它具有以下缺点:

  1. 相比硬编码的方法调用,使用 反射 API 非常慢
  2. 反射 API 能绕过类型安全检查

比起JDK动态代理、cglibJavassistByte Buddy在性能上具有优势。

  • 被代理类
public class Source {
public String hello(String name) { return null; }
}
  • 代理类
public class Target {
public static String hello(String name) {
return "Hello " + name + "!";
}
}
  • 使用
/**
* 委托方法调用
* @author ZYW
* @version 1.0
* @date 2020-03-02 22:58
*/

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

文章作者: 平常心
文章链接: http://blog.v5cn.cn/2020/03/27/RPC技术之动态代理/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 平常心