JDK动态代理,只能代理接口,为什么呢?我们从一个样例入手。
JDK动态代理样例
一个接口IHello.java:
1package com.example.demo.proxy.jdk; 2 3public interface IHello { 4 void sayHello(String name); 5}
接口实现类HelloImpl.java:
1package com.example.demo.proxy.jdk; 2 3public class HelloImpl implements IHello { 4 @Override 5 public void sayHello(String name) { 6 System.out.println("Hello " + name); 7 } 8}
一个调用处理器MyInvocationHandler.java:
1package com.example.demo.proxy.jdk; 2 3import java.lang.reflect.InvocationHandler; 4import java.lang.reflect.Method; 5 6public class MyInvocationHandler implements InvocationHandler { 7 8 private Object target; 9 10 public MyInvocationHandler(Object target){ 11 this.target = target; 12 } 13 14 @Override 15 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 16 System.out.println(">>>> Before method invocation"); 17 18 // execute the target method 19 Object result = method.invoke(target, args); 20 System.out.println(">>>> After method invocation"); 21 22 // return the original value 23 return result; 24 } 25}
主测试类JdkProxyDemo.java:
1package com.example.demo.proxy.jdk; 2 3import java.lang.reflect.Proxy; 4 5/** 6 * JDK dynamic proxy demo. 7 * Main implementation steps: 8 * 1. Implement the java.lang.reflect.InvocationHandler interface 9 * 2. Create customized InvocationHandler to receive method calls from interface 10 * 3. Create proxy object by Proxy.newProxyInstance() 11 * 4. Call method on proxy object 12 */ 13public class JdkProxyDemo { 14 public static void main(String[] args) throws Exception { 15 // Save the generated files during executing this program 16 // (class com.misc.ProxyGenerator has logic to use 'saveGeneratedFiles' attribute, 17 // open ProxyGenerator class for detail) 18 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 19 20 //2. Create customized InvocationHandler to receive method calls from interface 21 MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl()); 22 23 //3. Create proxy object by Proxy.newProxyInstance() 24 IHello hello = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, handler); 25 26 //4. Call method on proxy object 27 hello.sayHello("Doris"); 28 29 System.out.println(); 30 } 31}
执行结果:

可以看到,原始的接口实现类HelloImpl的sayHello()方法已经被代理了,在原始逻辑前后分别加入了新的逻辑。
源码分析
接下来从源码的角度分析,其中最重要的是这一句:
IHello hello = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, handler);
这里调用了java.lang.reflection.Proxy类的方法初始化了一个代理对象:

主要的逻辑就3步:
第1步. Class<?> cl = getProxyClass0(loader, intfs);
这一步是根据参数classLoader和指定的interfaces,从缓存中获取代理对象的Class对象,如果缓存中没有,就通过ProxyClassFactory创建:

这里的proxyClassCache是WeakCache类型:


这里的Factory是WeakCache类中的私有内部类:

在Proxy类中创建代理类缓存WeakCache的参数:

来看下ProxyClassFactory中创建代理类过程(apply()方法):

生成代理类的方法ProxyGenerator.generateProxyClass():

注意:这里的参数saveGeneratedFiles是获取的系统属性:
1private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged( 2 new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
在GetBooleanAction类中有个run方法,会通过设置给GetBooleanAction的属性(即这里的sun.misc.ProxyGenerator.saveGeneratedFiles)返回一个Boolean值,跟进去可以看到,最终是通过解析System.getProperties()的值返回的:

这就是一开始为了保存生成的代理类文件,通过代码设置这个属性的原因:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
第2步. final Constructor<?> cons = cl.getConstructor(constructorParams);
执行完第1步后,代理类的字节码已经由ProxyClassFactory类生成了,而且已经保存下来,我们打开项目的根目录,找到这个文件:

在IDEA中对它进行反编译:

可以看到,代理类$Proxy0有以下特点:
- 继承自JDK自带的Proxy类,有一个参数为InvocationHandler的构造函数(这正是这一步需要通过反射来获得的,用于生成代理对象)
- 覆盖了Object类的toString(), equals()和hashCode()方法,
- 实现了IHello的sayHello()方法
重点关注实现的sayHello()方法:
super.h.invoke(this, m3, new Object[]{var1});
里面调用了父类Proxy的InvocationHandler对象(属性h)的invoke方法,第一个参数this代表是代理对象本身,第二个参数是IHello.sayHello()方法,第三个参数是sayHello()的参数。
这就是说,代理对象会将sayHello()方法的调用,转发给InvocationHandler.invoke()方法。
第3步. return cons.newInstance(new Object[]{h});
用第2步取得的构造器初始化一个代理对象,并将参数InvocationHandler传递给代理对象用于初始化。
上面可以看到,在$Proxy0的构造函数中,将参数传递给了父类Proxy进行初始化。
因此在调用代理对象的sayHello()方法时,实际上此调用会转发给这个InvocationHandler(即前文的MyInvocationHandler)。
=========================分隔线=====================================
了解了动态代理的原理后,现在来重新分析开始的代码:
1//2. Create customized InvocationHandler to receive method calls from interface 2MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl()); 3 4//3. Create proxy object by Proxy.newProxyInstance() 5IHello hello = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, handler); 6 7//4. Call method on proxy object 8hello.sayHello("Doris");
2. 我们先创建了一个自定义的代理类MyInvocationHandler(继承自InvocationHandler),然后创建了一个对象handler,handler里面的target是一个HelloImpl对象。
3. 接下来用这个handler创建了一个IHello的代理对象hello。
4. 调用代理对象的sayHello("Doris")方法时,此调用转发给代理对象hello的InvocationHandler(即这里的handler对象)。
在handler执行的时候,调用到了这段代码:
1@Override 2public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 System.out.println(">>>> Before method invocation"); 4 5 // execute the target method 6 Object result = method.invoke(target, args); 7 System.out.println(">>>> After method invocation"); 8 9 // return the original value 10 return result; 11}
于是有了上述执行结果。
=========================分隔线=====================================
回答开始的问题:为什么JDK动态代理只能代理接口?
因为生成的代理类默认继承了JDK的Proxy类,由于Java是单继承,所以此代理类只能通过实现接口的方式扩展更多的方法,所以只能代理接口了。
=========================分隔线=====================================