侧边栏壁纸
博主头像
平常心的blog 博主等级

行动起来,活在当下

  • 累计撰写 12 篇文章
  • 累计创建 16 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
RPC

RPC技术之动态代理(一)

平常心
2024-10-16 / 0 评论 / 0 点赞 / 8 阅读 / 0 字

简介

在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

0

评论区