动态代理底层原理

siyue ... 基础
  • 基础
大约 3 分钟

# 简介

java里的class文件加载分为两种情况,一种就是类型是编译器已知的,这种文件的.class文件在编译的时候,编译器会把.class文件打开检查,但是注意不是加载哦,第二种就是我们可能是从别的地方获取到了一个引用,然后动态的把这个未知类型的引用的对象的.class文件加载进jvm虚拟机里。

那么我们称前者为RTTI,即Run- Time Type Identification 运行时类型识别,有的人把RTTI翻译成 Run - Time Type Information ,我个人认为是不对的,因为我觉得它概括的不够全面,所以我建议大家把I 翻译成Identification更容易理解。称后者为“反射”。

再简单说如果该类在编译前就已知,也就是该类在classPath路径下,这就是RTTI。如果该类编译器未知,也就是在程序运行时才知道的,这就是反射

# 原理:

被代理对象:

public interface EchoService {
     String  echo(String str);
}
1
2
3

被代理对象实例:

public class DefaultEchoService implements EchoService{
    @Override
 public String echo(String message) {
        return message;
    }
1
2
3
4
5

InvocationHandler 实现:

public class CostInvocationHandler implements InvocationHandler {
	private Object target;
    public CostInvocationHandler(Object target) {
        this.target = target;
    }
    
	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("cost " + cost);
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用:

public class test {
    public static void main(String[] args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Object proxy = Proxy.newProxyInstance(classLoader,
                new Class[]{EchoService.class},
                new CostInvocationHandler(new DefaultEchoService()));
        EchoService echoService = (EchoService) proxy;
         // cost 0
 		 // hello world
        System.out.println(echoService.echo("hello world"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12


# InvocationHandler

这个类必须有一个实现类,如上面的CostInvocationHandler。这个类里面只包含有一个 invoke 方法。

public interface InvocationHandler {
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
1
2
3

文档注释中,每一个代理对象必须有一个与之关联的invocation handler,当在代理实例上调用方法时,方法调用将被编码并发送到其调用实例的invoke方法。

拿上面例子解释:在执行 echo 方法的时候,就会调用 CostInvocationHandler中的invke方法,从而达到代理的目的。

# Proxy.newProxyInstance

这个方法中,最重要的就是生成代理对象。因为是动态生成的,所以需要利用HSDB (opens new window)去探究生成的字节码文件。下面就反编译后的文件:

public final class $Proxy0 extends Proxy implements EchoService {
    private static Method m1;
  	private static Method m3;
    private static Method m2;
    private static Method m0;
    
	public $Proxy0(InvocationHandler var1) {
        super(var1);
    }
    
	static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.aop.EchoService").getMethod("echo", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
  			m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  		 } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
         } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
         }
     }
     
	 public final String echo(String var1) {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        }  catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
   }
}

 //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
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

1、在 Proxy.newInstance 动态生成了 $Proxy0,并且这时候传给了构造函数 CostInvocationHandler; 2、$Proxy0的构造函数中,调用了 super();这时候把 CostInvocationHandler传给父类,并且赋值给 h;

public class Proxy{
 	protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
     }
}
1
2
3
4
5
6

3、调用 echo方法的时候,就会调用super.h.invoke(),即Proxy 类的h,也就是调用CostInvocationHandlerinvke方法。

这里invoke 方法又是 Method 的invke。反射的底层原理 (opens new window)

# 总结

jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。

参考: java动态代理实现与原理详细分析 (opens new window)