跳到主要内容

类代理方式

  • 反射: 有代理的地方几乎都有反射,它们是一套互相配合使用的功能类,在反射中可以调用方法、获取属性、拿到注解等相关内容,这些都可以与接下来的类代理组合使用,完成各种框架中的技术场景

五种类代理的方式

  1. JDK 代理

    public class JDKProxy {
    public static <T> T getProxy(Class clazz) throws Exception {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    return (T) Proxy.newProxyInstance(classLoader, new Class[]{clazz}, new InvocationHandler() {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(method.getName() + " 你被代理了,By JDKProxy!");
    return "结果";
    }
    });
    }
    }

    场景: 中间件开发、设计模式中的代理模式和装饰器模式应用
    点评: 这种JDK自带的类代理方式是非常常用的一种,也是非常简单的一种,基本会在一些中间件代码看到,比如数据库路由组件、Redis组件等,也可以应用到设计模式中

  2. CGLIB 代理

    public class CglibProxy implements MethodInterceptor {
    public Object newInstall(Object object) {
    return Enhancer.create(object.getClass(), this);
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("我被CglibProxy代理了");
    return methodProxy.invokeSuper(o, objects);
    }
    }

    场景: Spring、AOP切面、鉴权服务、中间件开发、RPC框架等
    点评: CGLIB 不同于 JDK,底层使用 ASM字节码框架 在类中修改指令码实现代理,因此这种代理方式不需要像 JDK 那样需要接口才能代理,同时得益于字节码框架的使用,速度也会快1.5-2.0倍

  3. ASM 代理方式

    public class ASMProxy extends ClassLoader {
    public static <T> T getProxy(Class clazz) throws Exception {
    ClassReader classReader = new ClassReader(clazz.getName());
    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);

    classReader.accept(new ClassVisitor(ASM5, classWriter) {
    @Override
    public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {

    // 方法过滤
    if (!"queryUserInfo".equals(name))
    return super.visitMethod(access, name, descriptor, signature, exceptions);

    final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);

    return new AdviceAdapter(ASM5, methodVisitor, access, name, descriptor) {

    @Override
    protected void onMethodEnter() {
    // 执行指令;获取静态属性
    methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    // 加载常量 load constant
    methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!");
    // 调用方法
    methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    super.onMethodEnter();
    }
    };
    }
    }, ClassReader.EXPAND_FRAMES);

    byte[] bytes = classWriter.toByteArray();

    return (T) new ASMProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
    }
    }

    场景: 全链路监控、破解工具包、CGLIB、Spring获取类元数据等
    点评: 使用字节码编程的规范进行处理,实现方式相对复杂,因为每一步代理操作都是在操作字节码指令,因此需要了解Java虚拟机规范相关的知识。但是是最接近底层的方式,也是最快的方式,在一些字节码插桩的全链路监控中非常常见

  4. Byte-Buddy 代理方式

    public class ByteBuddyProxy {
    public static <T> T getProxy(Class clazz) throws Exception {
    DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
    .subclass(clazz)
    .method(ElementMatchers.<MethodDescription>named("queryUserInfo"))
    .intercept(MethodDelegation.to(InvocationHandler.class))
    .make();

    return (T) dynamicType.load(Thread.currentThread().getContextClassLoader()).getLoaded().newInstance();
    }
    }

    @RuntimeType
    public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception {
    System.out.println(method.getName() + " 你被代理了,By Byte-Buddy!");
    return callable.call();
    }

    场景: AOP切面、类代理、组件、监控、日志
    点评: 也是一个字节码操作的类库,但是使用方式更加简单,无需理解字节码指令即可使用简单的API操作字节码控制类和方法。比起 JDK动态代理、CGLIBByte Buddy 在性能上具有一定的优势

  5. Javassise 代理方式

    public class JavassistProxy extends ClassLoader {
    public static <T> T getProxy(Class clazz) throws Exception {
    ClassPool pool = ClassPool.getDefault();
    // 获取类
    CtClass ctClass = pool.get(clazz.getName());
    // 获取方法
    CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
    // 方法前加强
    ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}");

    byte[] bytes = ctClass.toBytecode();

    return (T) new JavassistProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
    }
    }

    场景: 全链路监控、类代理、AOP
    点评: Javassist 是一个使用非常广的字节码插桩框架,几乎大部分非侵入的全链路监控都会使用这个框架,因为它不像 ASM 那样操作字节码导致风险,同时功能非常齐全,可以使用它所提供的方式直接编写插桩代码,也可以使用字节码指令进行控制生成代码

总结

  • 反射与代理:
    反射与代理
  • 代理的实际目的就是通过一些技术手段,替换掉原有的实现类或者非原有实现类注入新的字节码指令,而这些技术最终都会被应用到一些框架应用、中间件开发以及类似非侵入的全链路监控中