灵魂一问:为什么JDK动态代理只能代理接口?

近期在准备找一些新的工作机会,在网上看了一些面试常见问题,看看自己是否能比较好的回答。今天的这个问题:为什么JDK动态代理只能代理接口。这个问题看到次数挺多的,所以自己也深入研究了一下。

一些解答

在网上看了很多人的解答,大致有这么一些:

  • 是JDK动态代理是基于接口实现的,当你使用Proxy类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型
  • java是单继承的,JDK动态代理继承了Proxy类,无法同时继承被代理类,只能去实现被代理接口

其实这些答案我理解都是对的,但是都说的比较模糊,没有完全说透,自己使用idea写了例子,看了下生成的代理类,加深一些理解。

使用例子

在实际的开发中,使用动态代理,目的大多是为了对多个方法添加统一的增强逻辑,且不对原始代码做入侵。这里强调了多个,因为在我理解,如果只是为了对一个方法做增强,使用静态代理也可以做到不对原始代码做入侵,实现同样的效果。我们使用动态代理,就是为了这里的动态,对多个方法做统一增强,甚至是暂时还没有的方法,未来添加进来,也可以走到增强逻辑。

这里的使用例子,大致分成这么几个步骤:

  1. 定义接口 UserService,并定义2个方法
  2. 定义接口实现 UserServiceImpl,并实现上述2个方法
  3. 定义java.lang.reflect.InvocationHandler的一个实现类,在这个实现类中,对方法前后进行增强
  4. 在调用处使用Proxy.newProxyInstance来创建代理类,调用UserService接口中的2个方法

下面是这些实际的代码:

UserService
 

java

复制代码

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

UserServiceImpl
 

java

复制代码

package cn.pdf; import java.util.Arrays; import java.util.List; 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"); } }

MyHandler
 

java

复制代码

package cn.pdf; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; 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"); } }

Main
 

scss

复制代码

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的实现类,这个InvocationHandler的接口中,重点就是这个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目录下,如下图所示:

image.png

$Proxy0类的源码
 

java

复制代码

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package jdk.proxy1; import cn.pdf.UserService; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.util.List; 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个方法,其中hashCodeequalstoString应该是原始类从Object继承过来的,getNameByIdgetAllUserNameList是我们自己定义的方法。在这个类的静态代码块中,对着5个Method对象进行了赋值,供这个类中的5个方法分别使用。这个类的5个方法,核心是调用了super.h.invoke方法。因为这个$Proxy0类是继承自java.lang.reflect.Proxy类,这里的super.h自然也就是java.lang.reflect.Proxy类的h属性,我们可以打开这个类看下。

image.png

image.png

从上面2个图片中,可以看到,这个h是一个InvocationHandler类型的对象,这个值是在Proxy的构造方法中传入的,其实就是在我们调用Proxy.newProxyInstance的时候传入的myHandler对象,如下图中的源码所示

image.png

我的解答

通过上面的示例和源码解读,我们已经大致明白了JDK动态代理的过程,包括使用的方法、自动生成的源码、Proxy.newProxyInstance方法的调用等。我们回到今天开始的问题:为什么JDK动态代理只能代理接口?

我理解可以从这些方面来解答:

  1. 动态代理是为了做多个方法的增强,而我们在使用的地方,必须可以获取到代理对象,且可以使用被代理对象的类型来接收,就如果上面我们使用UserService类型的对象proxy来接收这个代理对象,这样我们才能调用原始的方法,否则,我们无法用原始对象接收的话,我们必须使用这个代理对象的类型来接收,但是我们事先是不知道这个自动生成的代理对象的类型的,如果事先知道的话,这里其实就退化成了静态代理。
  2. 既然要使用原始的类型来接收,那么在java中有2种方法:继承原始对象成为子类,或者 实现原始对象接口。
  3. JDK动态 代理自动生成对的类$Proxy0继承了java.lang.reflect.Proxy类,由于java是单继承的,所以这里没有机会再去继承被代理类。
  4. 所以,这里只剩下第2种方案,实现原始对象的接口。既然要实现原始对象的接口,那么原始的被代理的,只能是接口,不可以是类。

我们可以看出来,关键点在于,JDK动态代理自动生成的代理类在设计的时候,继承了java.lang.reflect.Proxy类,如果这里不是直接继承java.lang.reflect.Proxy类,而是设计了一个类似java.lang.reflect.Proxy类的接口,然后去实现这个接口,那就可以做到继承被代理的类。仔细查看了java.lang.reflect.Proxy类的内容,好像可以设计成java.lang.reflect.Proxy接口也没什么问题,特别是java支持接口中方法的default实现之后,这里做成接口也没什么特别的困难。

所以,这里JDK动态代理自动生成的类继承自java.lang.reflect.Proxy类,可能只是历史原因,当时技术方案设计就是这样,而且我们一般提倡面向接口编程,大多数情况下,动态代理只支持接口并没有什么问题,不过凡事总有例外,也许正式因为对类的动态代理的需求,cglib这种可以支持对类动态代理的库,才获得的了比较广泛的应用。

感谢大家耐心读完,对JDK动态代理自动生成的类为什么设计成继承自java.lang.reflect.Proxy类,而不是实现xxxProxy的接口,请在评论区说出你的理解,大家共同讨论提高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值