java的动态代理和字节码生成技术

引言

为什么要把动态代理和字节码生成技术一起说呢?因为动态代理的技术基石就是字节码生成技术,但是字节码生成技术能做的事情不止动态代理。

因为之前对Spring的使用不多,没有深入研究过源码,关于aop和动态代理等技术没有深入了解过,比如它到底是用来做什么的、又是怎么实现的。最近在工作中时不时会接触到这块概念,索性深入了解一下。

代理

什么是代理

英文似乎是Delegation,音译为代理。

其实就是本来程序提供了接口以及实现,但是在使用方调用的时候不是直接调用具体实现,而是有一个代理方,它被调用然后进行一些操作,并由它来调用真正的实现代码。

代理和组合是什么关系

上面提到的代理方真正实现 其实就是组合关系,这个是描述两个类之间的关系的,而代理 是用来描述两者之间的逻辑关系,他们只是用了组合这种方式来实现。

静态代理和动态代理

在网上搜索动态代理的资料,总是会有提到静态代理 ,但根据我的观察,这个只是为了说明代理形态,实际上动态代理是大家最终的选择,静态代理只是为了讲解而引入的。就像JVM的GC从没用过引用计数,但总是会提到这个思想,因为直观先想到的解决方法就是它。

下面是静态代理类的示例:

java 复制代码
// 接口
public interface HelloService {
    void sayHello(String name);
}
// 实现类
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}
// 静态代理类
public class HelloServiceProxy implements HelloService {

    private HelloService target; // 持有被代理对象

    public HelloServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void sayHello(String name) {
        System.out.println("【代理】方法执行前...");
        target.sayHello(name); // 调用真实方法
        System.out.println("【代理】方法执行后...");
    }
}
// 测试
public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        HelloService real = new HelloServiceImpl();
        // 创建代理对象,传入真实对象
        HelloService proxy = new HelloServiceProxy(real);
        // 通过代理调用
        proxy.sayHello("World");
    }
}

下面是动态代理的例子:

java 复制代码
// 接口
public interface HelloService {
    void sayHello(String name);
}
// 实现类
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}
// 实现 InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object target; // 被代理的真实对象

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【动态代理】方法执行前...");
        Object result = method.invoke(target, args); // 调用真实方法
        System.out.println("【动态代理】方法执行后...");
        return result;
    }
}

// 测试
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        HelloService real = new HelloServiceImpl();

        // 创建动态代理对象
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
            real.getClass().getClassLoader(),  // 类加载器
            real.getClass().getInterfaces(),   // 代理的接口
            new MyInvocationHandler(real)      // 处理器
        );

        // 通过代理调用
        proxy.sayHello("World");
    }
}

动态代理代码解析

总共其实是两个部分

  1. 实现InvocationHandler接口,加入代理逻辑
  2. 使用Proxy.newProxyInstance创建动态代理对象
    逻辑是确定的,不展开描述

动态代理的底层实现

这里基于问题跟进

  1. 动态代理到底是怎么让代理work起来的?

    从静态代理来看,其实是一个代理类调用具体实现类。

    但动态代理其实有三个类,需要新产生两个类,一个类A是切面类,实现要对被代理类的扩展操作;另一个类B 是动态生成的类,实现了指定的接口,同时继承Proxy类。

    他们的调用关系如下:类B调用 类A,类A调用真正实现类。

    "动态"的特点就体现在类B是运行时生成的,类A也不需要实现被代理类的接口。

  2. 真正实现类与切面类之间没有组合关系,切面类是如何调用真正实现类的方法的。

    答案很简单:反射。动态代理类调用切面类的时候把方法句柄传过去了。

  3. 动态代理类是怎么生成的

    是用了字节码生成技术,通过拼接字节码生成了一个动态类,我们经常在堆栈中看到的Proxy0就是。

    HelloService proxy = (HelloService) Proxy.newProxyInstance(

    real.getClass().getClassLoader(), // 类加载器

    real.getClass().getInterfaces(), // 代理的接口

    new MyInvocationHandler(real) // 处理器

    );

    方法调用里的参数也可以看到,需要指定用哪个类加载器加载动态生成的字节码;然后要指定接口,动态生成的字节码要实现这个接口;至于第三个就是切面对象,代理类会调用它。

切面是什么

我觉得切面其实就是在方法前后可以做一些事情,不是通过正常编程方式,而是在已有的代码里塞进去一些逻辑,所以叫做切面编程。

动态代理其实就是切面编程,InvocationHandler里实现的就是切面的逻辑。

但切面其实还更广泛,如果直接在已有的代码上通过字节码增强的方式加入逻辑,也是切面编程,但就不属于动态代理的范畴了,Aspectj就是切面编程的一个工具,这种又有代码的编译时静态织入和运行时动态织入两种形态。

基于动态代理,对RPC的理解

RPC框架其实就是用动态代理生成了业务接口的实现类,可以想象它底层做了什么。

  1. 动态代理类实现了业务接口,让调用方感觉像是在调用接口的具体实现类
  2. 内部做了request构造、远程调用、response解析等逻辑,让IO操作对调用方进行了屏蔽。
    所以RPC框架其实就是对这种IO调用的一种统一解决方案,避免了每个调用方都要自己写IO连接、请求、返回处理等逻辑,而且用动态代理的好处是RPC框架是通用的,只要指定一个接口,RPC可以表现成对这个接口的实现。

CGLIB

除了上面JDK的Proxy,还有一种动态代理技术是CGLIB(code generation library)

它俩的区别是JDK的Proxy是基于接口生成动态类,CGLIB是继承父类实现动态类。Spring同时支持这两种。

字节码生成技术

上面的动态代理底层都是要动态生成代理类的,那这个代理类的生成都是要用字节码生成技术的。

这个技术有多种实现,JDK的Proxy是用的自行实现的字节码拼接逻辑,除此之外还有ASM、java的Instrumentation API、byteBuddy。

这几个不展开介绍了,暂不是很了解。

他们有很强的字节码操作功能,不只是生成,应该还有修改。

Arthas应该就用了Instrumentation API,所以这个技术的使用范围不止是动态代理,能做的事情很多。

关于java的编译 + 解释执行

感觉字节码生成技术是依托于java的这个特性的,java的编译结果是class,class是有固定结构的,那就存在生成、拼接、修改等灵活操作的空间。这些修改动作其实就是字节码生成。

然后生成出来的class最终被ClassLoader加载去解释执行,这么一想,其实这项技术也没有什么神秘的地方,只不过在平常业务开发中并不是那么常用罢了。

相关推荐
eggwyw1 小时前
基于SpringBoot和PostGIS的云南与缅甸的千里边境线实战
java·spring boot·spring
0xDevNull1 小时前
MySQL 别名(Alias)指南:从入门到避坑
java·数据库·sql
lv__pf2 小时前
springboot原理
java·spring boot·后端
java1234_小锋2 小时前
Java高频面试题:什么是可重入锁?
java·开发语言
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【22】Agent 并行工具执行与超时 / 协作式取消实战
java·人工智能·spring
段小二2 小时前
服务一重启全丢了——Spring AI Alibaba Agent 三层持久化完整方案
java·后端
段小二3 小时前
Agent 自动把机票改错了,推理完全正确——这才是真正的风险
java·后端
itjinyin3 小时前
ShardingSphere-jdbc 5.5.0 + spring boot 基础配置 - 实战篇
java·spring boot·后端
丶小鱼丶3 小时前
Java虚拟机【JVM】
java·jvm