你有没有思考过:为什么JDK动态代理仅能代理接口?这个问题已被广泛讨论,Java面试也会经常被问到,这里我们通过实际案例结合源码分析进行了深入研究。

网上解答

在网络上浏览了多种解答,总结起来大致如下:

1)JDK动态代理基于接口实现,使用Proxy创建代理对象时,需要指定一个接口列表,这些接口定义了代理对象应实现的方法,成为代理对象的类型。

2)由于Java的单继承限制,JDK动态代理已继承Proxy类,无法同时继承被代理的类,只能通过实现接口来实现代理。

这些解释在我看来都是正确的,尽管表述有些模糊。我为了更好地理解,亲自编写了示例代码,并查看生成的代理类,从中加深了对这些概念的理解。

案例说明

实际开发中,动态代理常用于为多个方法添加统一的增强逻辑,而不改动原始代码。值得强调的是这里的“多个”,因为在我看来,若只需增强一个方法,静态代理同样能够实现不改动原代码的效果。我们之所以选择动态代理,是因为它具有动态性,能够为多个方法添加增强逻辑,甚至是尚未存在的方法,将来也能适用增强逻辑。

以下是案例的大致步骤:

1)定义接口UserService,并定义了两个方法。

public interface UserService { /** * 根据用户ID获取用户姓名 * @param id 用户id * @return 用户姓名 */ String getNameById(Long id); /** * 获取所有的用户名列表 * @return 所有的用户名列表 */ List<String> getAllUserNameList(); } 

2)实现接口UserServiceImpl,实现了上述两个方法。

public class UserServiceImpl implements UserService { @Override public String getNameById(Long id) { System.out.println("invoke getNameById return foo"); return "foo"; } @Override public List<String> getAllUserNameList() { System.out.println("invoke getAllUserNameList return list"); return Arrays.asList("foo", "bar"); } } 

3)编写一个实现java.lang.reflect.InvocationHandler的类,在其中为方法前后添加增强逻辑。

public class MyHandler implements InvocationHandler { private Object target; public MyHandler(Object target) { this.target = target; } @Override public Object invoke(Object o, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void before() { System.out.println("before"); } private void after() { System.out.println("after"); } }

4)在调用处使用Proxy.newProxyInstance来创建代理类,调用UserService接口中的两个方法。

public static void main(String[] args) { // 保存自动生成的动态代理的类 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); // 1. 创建被代理的对象,UserService接口的实现类 UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2. 获取对应的 ClassLoader ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService, Class[] interfaces = userServiceImpl.getClass().getInterfaces(); // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用 MyHandler myHandler = new MyHandler(userServiceImpl); UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, myHandler); // 调用代理类的方法 proxy.getNameById(1L); proxy.getAllUserNameList(); } 

源码解析

UserService和UserServiceImpl是初始接口和实现类。重点分析MyHandler和Main。

MyHandler

MyHandler是java.lang.reflect.InvocationHandler的实现。在其接口中,关键是invoke方法。在invoke方法中,我们使用method.invoke(target, args)来调用原始接口。我们可以在其前后执行自定义增强操作,如日志记录、参数分支逻辑、耗时计算等。

Main

这是调用部分,重要的代码是创建代理对象:UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, myHandler);。我们创建了代理对象,用一个原始接口UserService的引用proxy来接收。随后,调用proxy的方法实际上是调用了代理对象的方法,从而进入增强逻辑。

上述内容是对动态代理使用代码的解释。但要回答问题,关键在于动态生成的代理类源代码。在Main中,我们通过System.getProperties().put(“jdk.proxy.ProxyGenerator.saveGeneratedFiles”, “true”);来保存生成的代理类文件。让我们关注生成的文件,它位于原工程的jdk/proxy1目录下。如下图所示:

$Proxy0类的源码

public final class $Proxy0 extends Proxy implements UserService { private static final Method m0; private static final Method m1; private static final Method m2; private static final Method m3; private static final Method m4; public $Proxy0(InvocationHandler var1) { super(var1); } public final int hashCode() { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final boolean equals(Object var1) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String getNameById(Long var1) { try { return (String)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final List getAllUserNameList() { try { return (List)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("cn.pdf.UserService").getMethod("getNameById", Class.forName("java.lang.Long")); m4 = Class.forName("cn.pdf.UserService").getMethod("getAllUserNameList"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException { if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) { return MethodHandles.lookup(); } else { throw new IllegalAccessException(var0.toString()); } } } 

可以观察到,在这个类中,最初定义了5个final的Method对象m0~m4,它们分别表示原始类的5个方法。其中,hashCode、equals和toString方法是从Object类继承而来的,而getNameById和getAllUserNameList是我们自己定义的方法。

在这个类的静态代码块中,对这5个Method对象进行了赋值,以供这个类的5个方法分别使用。这个类的5个方法的核心是调用了super.h.invoke方法。因为这个$Proxy0类是继承自java.lang.reflect.Proxy类,所以这里的super.h自然就是java.lang.reflect.Proxy类的h属性。如果有需要,我们可以打开这个类并查看其内容。 结合上图,可以看出在上述的代码中,h是一个InvocationHandler类型的对象。这个值是在Proxy的构造方法中传入的,实际上就是在调用Proxy.newProxyInstance时传入的myHandler对象。您可以参考源码来进一步了解相关细节。

综合解答

通过之前的示例和源码解读,我们对JDK动态代理的过程有了初步的了解,包括使用的方法、自动生成的源码以及Proxy.newProxyInstance方法的调用等。现在我们回到最初的问题:为什么JDK动态代理只能代理接口?

我认为可以从以下几个方面来解答这个问题:

  1. 动态代理的目的是对多个方法进行增强,在使用代理对象时,我们需要能够获取到代理对象并使用原始对象的类型来接收。如果我们使用UserService类型的对象proxy来接收代理对象,这样我们才能调用原始的方法。否则,如果我们无法使用原始对象接收代理对象,那我们必须使用代理对象的类型来接收,但是事先我们并不知道自动生成的代理对象的具体类型。如果我们事先知道代理对象的类型,那么就变成了静态代理的情况。
  2. 如果要使用原始对象的类型来接收代理对象,Java中有两种方法:继承原始对象成为子类,或者实现原始对象的接口。JDK动态代理自动生成的类$Proxy0继承了java.lang.reflect.Proxy类,由于Java的单继承特性,这里没有机会再去继承被代理类。
  3. 因此,只剩下第二种方案,即实现原始对象的接口。由于要实现原始对象的接口,所以被代理的对象只能是接口,而不能是类。
  4. 我们可以观察到,关键点在于JDK动态代理自动生成的代理类继承自java.lang.reflect.Proxy类。如果不是直接继承java.lang.reflect.Proxy类,而是设计一个类似java.lang.reflect.Proxy类的接口,然后实现这个接口,就可以实现对被代理类的继承。仔细查看java.lang.reflect.Proxy类的内容,可以发现设计成java.lang.reflect.Proxy接口也没有什么问题,特别是在Java支持接口中默认方法实现之后,这样的设计也并不困难。

综上所述,JDK动态代理自动生成的类继承自java.lang.reflect.Proxy类可能只是出于历史原因,当时的技术方案设计就是这样。而且我们通常倡导面向接口编程,在大多数情况下,动态代理只支持接口并没有问题。然而,总有例外情况,正因为对类的动态代理的需求,才出现了像cglib这样的库,它可以支持对类的动态代理,并且获得了广泛的应用。