动态代理

最近研究 mybatis 是怎么将 mapper 的方法结合着 .xml 的 sql 进行绑定的,发现里面有很多动态代理的调用学了学,看了看源码,记录一下。

# Demo

public class LoggerInvocationHandler implements InvocationHandler {

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

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      	// 前置输出参数
        String methodDesc = String.format("[LOGGER] %s#%s",
                target.getClass().getName(),
                method.getName());
        System.out.println(methodDesc + ", args:" +
                (args==null||args.length==0
                        ? "EMPTY"
                        : StringUtils.join(Arrays.stream(args).map(Object::toString).collect(Collectors.toList()), ',')));
				// 执行真实方法
        Object ret = method.invoke(target, args);
      	// 后置输出返回值
        System.out.println(methodDesc + ", return:" + ret);
      	
      	return ret;
    }
}


public interface Subject {
    public void doSomething ();
}
public class RealSubject implements Subject {
    @Override
    public void doSomething () {
        System.out.println("RealSubject doSomething() ...");
    }
}


public class Main {

    public static void main(String[] args) {
      	// 接收到方法转发的处理器
        RealSubject realSubject = new RealSubject();
        LoggerInvocationHandler handler = new LoggerInvocationHandler(realSubject);

      	// 动态代理类
        Subject subject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                RealSubject.class.getInterfaces(),
                handler
        );
        subject.doSomething();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

输出:

[LOGGER] com.snopzyz.code4learn.动态代理.RealSubject#doSomething, args:EMPTY
RealSubject doSomething() ...
[LOGGER] com.snopzyz.code4learn.动态代理.RealSubject#doSomething, return:null
1
2
3

# 源码分析代理逻辑

下面从 JDK 源码入手,围绕 Demo 中调用的 java.lang.reflect.Proxy#newProxyInstance 方法内的逻辑进行讲解

其中

  • java.lang.reflect.Proxy#getProxyClass0
    • java.lang.reflect.WeakCache#get
      • java.lang.reflect.WeakCache.Factory#get(从supplier.get那一行来的)
        • java.lang.reflect.Proxy.ProxyClassFactory#apply
          • sun.misc.ProxyGenerator#generateProxyClass
            • java.lang.reflect.ProxyGenerator#generateClassFile

生成了个名为 “com.sun.proxy.$Proxy0” 这样的类,有 newProxyInstance 中 interfaces 中所有的方法
methods.add(generateConstructor()); 一行可以看到,加了一个参数为 InvocationHandler 的构造方法
并通过 dout.writeShort(cp.getClass(superclassName)); 标注父类是 java.lang.reflect.Proxy
并通过

// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
    dout.writeShort(cp.getClass(dotToSlash(intf.getName())));
}
1
2
3
4
5
6

标注是实现自 newProxyInstance 中的 interface

因此 newProxyInstance 中的 Class<?> cl = getProxyClass0(loader, intfs); cl 是一个继承了 Proxy、实现了 Subject(我Demo中的类名)的类
因此该方法返回值 return cons.newInstance(new Object[]{h}); 可以被强转成 Subject,但同时它又的确是 Proxy 的子类

那么Proxy的方法被执行时是怎么发到InvocationHandler中的?
这个问题可以从上面最后一个方法 java.lang.reflect.ProxyGenerator#generateClassFile 继续往下看。

// 遍历代理接口,将接口方法添加到代理类中
for (Class<?> intf : interfaces) {
    for (Method m : intf.getMethods()) {
        if (!Modifier.isStatic(m.getModifiers())) {
            addProxyMethod(m, intf);
        }
    }
}
1
2
3
4
5
6
7
8

在这里的 addProxyMethod 中会将interface的所有方法收录进一个成员变量 proxyMethods 中,然后在下面实际将这些方法添加进代理类的方法中时

for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    for (ProxyMethod pm : sigmethods) {
        // add static field for method's Method object
        fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
        // generate code for proxy method and add it
        methods.add(pm.generateMethod());
    }
}
1
2
3
4
5
6
7
8

在这里面的 generateMethod 就是生成实际方法的过程,generateMethod 中从 out.writeShort(cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + "[Ljava/lang/Object;)Ljava/lang/Object;")); 这一行就可以看出,生成的每个实际方法,都是只有一个 invocationHandler.invoke 的调用,没有其他内容了。

等于说 Demo 中的 subject 对象,它真实的类被定义成下面这样

package com.sun.proxy;

public class $Proxy0 extend Proxy implements Subject {
    // 存储每个方法的Method对象
    // ...... hasCode、equals、toString 就不多说了
    private static Method m4;  // doSomething
    
    // 静态代码块初始化Method对象
    static {
        try {
            // ...... 一样不说  hasCode 这些了
            m4 = Class.forName("com.动态代理学习.Subject").getMethod("doSomething");
        } catch(NoSuchMethodException | ClassNotFoundException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
    
    // 构造方法必须调用父类构造器
    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    
    // 实现接口方法
    public void doSomething() {
        try {
            // 调用InvocationHandler的invoke方法
            super.h.invoke(
                this,           // 代理对象本身
                m4,             // doSomething方法对象
                null            // 无参数
            );
        } catch (Throwable e) {
            // 处理异常
            throw new UndeclaredThrowableException(e);
        }
    }
    
    // ......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

所以说代理类是方法的转发,但我更想用一个歧义不大的名词:“代替”。

# 总结

动态代理是指用 Proxy.newProxyInstance 动态生成一个对象

  • 类:继承自 Proxy、实现自newProxyInstance参数2(interfaces)

  • 对象:传入了参数3(invocationHandler)进行构造

这个对象中所有来自 interfaces 的方法名保留,内容都替换成 invocationHandler.invoke。

从而做到:调用 newProxyInstance 方法的返回对象的任何方法时,都会只去执行 invocationHandler.invoke

# 应用

正如开头所说,mybatis 的动态代理,它在 spring bean 加载时将 xml 的 sql 解析出来,并和 selectOne、delete 这样的方法通过动态代理绑定。这些方法

Last Updated: 11/4/2025, 7:50:50 PM