JDK实现动态代理

​​​​​众所周知,jdk动态代理需要实现目标类相同的接口,并对其方法进行增强

newProxyInstance()是Proxy提供的一个静态方法,用于动态创建代理对象

typescript 复制代码
public class ProxyFactory {
    // 目标对象
    private final Object target;
    
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    /**
     * 获取代理对象(静态方法,直接调用)
     */
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),    // 类加载器
            target.getClass().getInterfaces(),     // 接口列表
            new InvocationHandler() {              // 处理器
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {              
                    // 记录开始时间
                    long startTime = System.currentTimeMillis();                    
                    Object result = null;
                    result = method.invoke(target, args);
                    long endTime = System.currentTimeMillis();                    
                    return result;
                }
                
            }
        );
    }
}

Proxy.newProxyInstance() 做了三件事:

Proxy.newProxyInstance(classLoader, interfaces, handler);

  1. 动态生成字节码 :在内存中根据你传入的接口,动态创建一个新的类(这个类实现了你传入的所有接口)。
  2. 加载到 JVM :使用传入的 classLoader 将这个新类的字节码加载到 JVM 内存中。
  3. 创建实例 :实例化这个代理类,并将 InvocationHandler 绑定到它上面。

动态代理3大参数详解

类加载器:

  • 动态生成的字节码只是内存中的一段二进制数据(byte[]
  • JVM 无法识别和使用它
  • 必须通过类加载器将它转换为 Class 对象,才能 newInstance() 创建实例

JVM 会根据newProxyInstance的方法信息,在内存中动态生成 .class 文件的二进制数据:

scss 复制代码
// 以下不是真实代码,而是 JDK 内部字节码生成逻辑的伪代码示意
public byte[] generateProxyClass(Class<?>[] interfaces) {
    StringBuilder byteCode = new StringBuilder();

    // 1. 类头:继承 Proxy,实现指定接口
    byteCode.append("public final class $Proxy0 extends Proxy implements " + interfaces[0].getName() + " {\n");

    // 2. 为每个方法生成一个静态的 Method 对象(用于反射调用)
    for (Method m : interfaces[0].getMethods()) {
        byteCode.append("    private static Method m" + count++ + ";\n");
    }

    // 3. 生成构造方法(接收 InvocationHandler)
    byteCode.append("    public $Proxy0(InvocationHandler h) { super(h); }\n");

    // 4. 为每个接口方法生成实现(重点!)
    for (Method m : interfaces[0].getMethods()) {
        byteCode.append("    public " + m.getReturnType() + " " + m.getName() + "(");
        // 参数列表...
        byteCode.append(") {\n");
        byteCode.append("        try {\n");
        // 核心:调用 super.h.invoke(this, mX, args);
        byteCode.append("            return (" + m.getReturnType() + ") super.h.invoke(this, m" + index + ", args);\n");
        byteCode.append("        } catch (Throwable e) { throw new UndeclaredThrowableException(e); }\n");
        byteCode.append("    }\n");
    }

    byteCode.append("}");
    return byteCode.toString().getBytes(); // 返回字节码二进制
}

加载并实例化

ini 复制代码
// 1. 用 ClassLoader 将上面生成的 byte[] 加载为 Class 对象
Class<?> proxyClass = classLoader.defineClass("$Proxy0", byteCode, 0, byteCode.length);

// 2. 获取构造方法
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);

// 3. 实例化代理对象,传入你的 InvocationHandler
Object proxy = constructor.newInstance(handler);

接口列表:

  • 告诉 JVM 代理类需要实现哪些接口的方法(方法名、参数类型、返回值类型、异常声明)
  • 让代理对象可以安全转型(Cast)为接口类型,否则无法赋值给接口变量
    • SellTickets proxy = (SellTickets) Proxy.newProxyInstance(...)
  • JVM 在生成 $Proxy0.class 字节码时,必须知道要生成哪些方法,接口列表就是施工图纸

station.getClass().getInterfaces()获取目标类的接口Class对象列表,一一实现其方法(调用的是代理对象的调用处理程序)InvocationHandler.invoke(proxy, method, args)

InvocationHandler

动态代理的大脑和心脏

如果说前两个参数(类加载器、接口列表)都是"骨架",那InvocationHandler 才是真正干活的部分

它是连接代理对象和目标对象的桥梁------所有对代理对象的方法调用,最终都会被转发到 InvocationHandler invoke() 方法中,由你决定怎么处理(增强、转发、拦截等)。

java 复制代码
package java.lang.reflect;

@FunctionalInterface
public interface InvocationHandler {
    /**
     * 处理代理对象上的方法调用并返回结果
     *
     * @param proxy  代理对象本身(就是你调用方法的那个对象)
     * @param method 被调用的方法的 Method 对象(反射)
     * @param args   方法调用时传入的参数数组
     * @return       方法调用的返回值
     * @throws Throwable 可以抛出任何异常
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

InvocationHandler的invoke便是动态代理的核心:

  • 第一个参数为代理对象本身,用的较少,容易无限递归
  • 第二个参数为要调用的方法的反射对象,最后通过method.invoke(target, args) 调用目标方法
  • 第三个参数为目标方法调用时传入的实际参数

职责一:转发真正的目标对象方法

java 复制代码
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 把方法调用转发给真实对象 station
        return method.invoke(station, args);
    }
};

职责二:增强逻辑(AOP本质)

csharp 复制代码
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ========== 前置增强 ==========
        System.out.println("【日志】" + method.getName() + " 方法开始执行,参数:" + Arrays.toString(args));
        System.out.println("【事务】开启事务");
        
        try {
            // ========== 转发给目标对象 ==========
            Object result = method.invoke(station, args);
            
            // ========== 后置增强(正常返回) ==========
            System.out.println("【事务】提交事务");
            System.out.println("【日志】" + method.getName() + " 方法执行成功,返回值:" + result);
            return result;
            
        } catch (Exception e) {
            // ========== 后置增强(异常返回) ==========
            System.out.println("【事务】回滚事务");
            System.out.println("【日志】" + method.getName() + " 方法执行异常:" + e.getMessage());
            throw e;
        }
    }
};

职责三:完全拦截(Mybatis)

java 复制代码
// MyBatis 的 Mapper 代理简化版
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 从方法上提取 SQL 注解
        String sql = method.getAnnotation(Select.class).value();
        
        // 2. 执行 JDBC 查询
        ResultSet rs = jdbcTemplate.query(sql, args);
        
        // 3. 将 ResultSet 映射为对象返回
        return mapResultSetToObject(rs, method.getReturnType());
        
        // ⚠️ 注意:这里没有调用任何目标对象!
        // 因为 Mapper 本身就没有实现类!
    }
};

通过反编译内存中动态生成的代理类,我们可以更好的理解动态代理:

反编译后的动态代理
scala 复制代码
public final class $Proxy0 extends Proxy implements SellTickets {

    private static Method m3; // sell
    
    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }
    
    public final void sell() throws  { 
            super.h.invoke(this, m3, (Object[])null);
    }
    // ... 其他方法
}

我们来看这一部分:

ruby 复制代码
public $Proxy0(InvocationHandler var1) {
        super(var1);
}

在构造时将InvocationHandler传递给父类的构造器,我们再来看看父类Proxy做了什么

java 复制代码
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
}

父类Proxy将其传递的InvocationHandler赋给自身变量,同时我们再看反编译的代码的这一部分:

java 复制代码
public final void sell() throws  { 
    super.h.invoke(this, m3, (Object[])null);
}

在这里调用了父类的InvocationHandler的invoke方法,实现真正的动态代理核心

  1. 创建 InvocationHandler
  2. 调用 Proxy.newProxyInstance(loader, interfaces, h)
  3. 生成代理类 $Proxy0,构造方法接收 InvocationHandler
  4. 通过构造方法将 h 传给父类 Proxy

    public class Proxy {
    protected InvocationHandler h;
    protected Proxy(InvocationHandler h) {
    this.h = h;
    }
    }
  5. $Proxy0 的父类 Proxy 中保存了 h
  6. 调用 $Proxy0.sell() → super.h.invoke(...)
  7. 最终执行你的 InvocationHandler 逻辑

现在我们知道了,动态代理通过实现接口列表,并通过类加载器将二进制数据转为Class字节码对象(JVM才能看懂)

其核心方法为调用InvocationHandler的invoke方法(通过传入的方法对象,再在内部invoke调用)

newProxyInstance 是怎么组装这些参数的?

swift 复制代码
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    throws IllegalArgumentException {

    // 根据接口列表生成代理类的字节码并加载
    Class<?> cl = getProxyClass0(loader, intfs);
    
    // 代理类的构造方法签名:public $Proxy0(InvocationHandler h)
    final Constructor<?> cons = cl.getConstructor(constructorParams);

    // 用构造方法创建实例,传入 InvocationHandler
    return cons.newInstance(new Object[]{h});
}
  • 先通过 ProxyGenerator 根据接口列表在内存中生成 $Proxy0 类的字节码
  • 然后用 ClassLoader 加载为 Class 对象
  • 再获取其构造方法(接收 InvocationHandler 参数)核心逻辑
  • 最后通过构造方法将 InvocationHandler 传入并实例化

Spring 的 ProxyFactory(官方提供)

java 复制代码
import org.springframework.aop.framework.ProxyFactory;

public class SpringProxyDemo {
    public static void main(String[] args) {
        Station station = new Station();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(station);
        factory.addAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("【Spring AOP】前置增强");
                Object result = invocation.proceed();
                System.out.println("【Spring AOP】后置增强");
                return result;
            }
        });
        SellTickets proxy = (SellTickets) factory.getProxy();
        proxy.sell();
    }
}

相关推荐
袋鱼不重2 小时前
解决 Web 端图片预览与下载颜色不一致的一种工程方案
前端·后端
lizhongxuan2 小时前
Agent 的 Code-driven Assembly
后端
稀土熊猫君3 小时前
一个人能做出什么开源项目?
vue.js·后端·开源
lizhongxuan3 小时前
Agent Runtime 中的 Code-driven Assembly
后端
货拉拉技术3 小时前
资损下降 99.96% 的背后: AI 资损防控平台实战
后端
用户8358086187913 小时前
撮合引擎 OrderBook 的 100ns 之路:无锁 RingBuffer + 伪共享消除,Go 1.22 下单 op 11ns
后端
用户881863001363 小时前
用Node.js写一个简单的API请求日志中间件
后端