JDK动态代理可以不基于接口吗?

JDK动态代理可以不基于接口吗?
答案是:不行!
 
举个简单的例子在分析原因之前,我们先完整的看一下实现jdk动态代理需要几个步骤,首先需要定义一个接口:
public interface Worker {void work();}再写一个基于这个接口的实现类:
public class Programmer implements Worker {@Overridepublic void work() {System.out.println("coding...");}}自定义一个Handler,实现InvocationHandler接口,通过重写内部的invoke方法实现逻辑增强 。其实这个InvocationHandler可以使用匿名内部类的形式定义,这里为了结构清晰拿出来单独声明 。
public class WorkHandler implements InvocationHandler {private Object target;WorkHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("work")) {System.out.println("before work...");Object result = method.invoke(target, args);System.out.println("after work...");return result;}return method.invoke(target, args);}}在main方法中进行测试,使用Proxy类的静态方法newProxyInstance生成一个代理对象并调用方法:
public static void main(String[] args) {Programmer programmer = new Programmer();Worker worker = (Worker) Proxy.newProxyInstance(programmer.getClass().getClassLoader(),programmer.getClass().getInterfaces(),new WorkHandler(programmer));worker.work();}执行上面的代码,输出:
before work...coding...after work...可以看到,执行了方法逻辑的增强,到这,一个简单的动态代理过程就实现了,下面我们分析一下源码 。
Proxy源码解析既然是一个代理的过程,那么肯定存在原生对象和代理对象之分,下面我们查看源码中是如何动态的创建代理对象的过程 。上面例子中,创建代理对象调用的是Proxy类的静态方法newProxyInstance,查看一下源码:
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});}//省略catch}概括一下上面代码中重点部分:

  • 在checkProxyAccess方法中,进行参数验证
  • 在getProxyClass0方法中,生成一个代理类Class或者寻找已生成过的代理类的缓存
  • 通过getConstructor方法,获取生成的代理类的构造方法
  • 通过newInstance方法,生成实例对象,也就是最终的代理对象
上面这个过程中,获取构造方法和生成对象都是直接利用的反射,而需要重点看看的是生成代理类的方法getProxyClass0 。
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing// the given interfaces exists, this will simply return the cached copy;// otherwise, it will create the proxy class via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces);}注释写的非常清晰,如果缓存中已经存在了就直接从缓存中取,这里的proxyClassCache是一个WeakCache类型,如果缓存中目标classLoader和接口数组对应的类已经存在,那么返回缓存的副本 。如果没有就使用ProxyClassFactory去生成Class对象 。中间的调用流程可以省略,最终实际调用了ProxyClassFactory的Apply方法生成Class 。在apply方法中,主要做了下面3件事 。
  • 首先,根据规则生成文件名:
if (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/* * Choose a name for the proxy class to generate. */long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;如果接口被定义为public公有,那么默认会使用com.sun.proxy作为包名,类名是$Proxy加上一个自增的整数值,初始时是0,因此生成的文件名是$Proxy0 。


推荐阅读