JDK动态代理,只能代理接口,为什么呢?我们从一个样例入手。
JDK动态代理样例
一个接口IHello.java:
package com.example.demo.proxy.jdk;
public interface IHello {
void sayHello(String name);
}
接口实现类HelloImpl.java:
package com.example.demo.proxy.jdk;
public class HelloImpl implements IHello {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
一个调用处理器MyInvocationHandler.java:
package com.example.demo.proxy.jdk;
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(">>>> Before method invocation");
// execute the target method
Object result = method.invoke(target, args);
System.out.println(">>>> After method invocation");
// return the original value
return result;
}
}
主测试类JdkProxyDemo.java:
package com.example.demo.proxy.jdk;
import java.lang.reflect.Proxy;
/**
* JDK dynamic proxy demo.
* Main implementation steps:
* 1. Implement the java.lang.reflect.InvocationHandler interface
* 2. Create customized InvocationHandler to receive method calls from interface
* 3. Create proxy object by Proxy.newProxyInstance()
* 4. Call method on proxy object
*/
public class JdkProxyDemo {
public static void main(String[] args) throws Exception {
// Save the generated files during executing this program
// (class com.misc.ProxyGenerator has logic to use 'saveGeneratedFiles' attribute,
// open ProxyGenerator class for detail)
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//2. Create customized InvocationHandler to receive method calls from interface
MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl());
//3. Create proxy object by Proxy.newProxyInstance()
IHello hello = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, handler);
//4. Call method on proxy object
hello.sayHello("Doris");
System.out.println();
}
}
执行结果:
可以看到,原始的接口实现类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是获取的系统属性:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(
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)。
=========================分隔线=====================================
了解了动态代理的原理后,现在来重新分析开始的代码:
//2. Create customized InvocationHandler to receive method calls from interface
MyInvocationHandler handler = new MyInvocationHandler(new HelloImpl());
//3. Create proxy object by Proxy.newProxyInstance()
IHello hello = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, handler);
//4. Call method on proxy object
hello.sayHello("Doris");
2. 我们先创建了一个自定义的代理类MyInvocationHandler(继承自InvocationHandler),然后创建了一个对象handler,handler里面的target是一个HelloImpl对象。
3. 接下来用这个handler创建了一个IHello的代理对象hello。
4. 调用代理对象的sayHello("Doris")方法时,此调用转发给代理对象hello的InvocationHandler(即这里的handler对象)。
在handler执行的时候,调用到了这段代码:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>> Before method invocation");
// execute the target method
Object result = method.invoke(target, args);
System.out.println(">>>> After method invocation");
// return the original value
return result;
}
于是有了上述执行结果。
=========================分隔线=====================================
回答开始的问题:为什么JDK动态代理只能代理接口?
因为生成的代理类默认继承了JDK的Proxy类,由于Java是单继承,所以此代理类只能通过实现接口的方式扩展更多的方法,所以只能代理接口了。
=========================分隔线=====================================