近期在准备找一些新的工作机会,在网上看了一些面试常见问题,看看自己是否能比较好的回答。今天的这个问题:为什么JDK动态代理只能代理接口。这个问题看到次数挺多的,所以自己也深入研究了一下。
一些解答
在网上看了很多人的解答,大致有这么一些:
- 是JDK动态代理是基于接口实现的,当你使用
Proxy
类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型 - java是单继承的,JDK动态代理继承了
Proxy
类,无法同时继承被代理类,只能去实现被代理接口
其实这些答案我理解都是对的,但是都说的比较模糊,没有完全说透,自己使用idea写了例子,看了下生成的代理类,加深一些理解。
使用例子
在实际的开发中,使用动态代理,目的大多是为了对多个
方法添加统一的增强逻辑,且不对原始代码做入侵。这里强调了多个
,因为在我理解,如果只是为了对一个方法做增强,使用静态代理也可以做到不对原始代码做入侵,实现同样的效果。我们使用动态代理,就是为了这里的动态
,对多个
方法做统一增强,甚至是暂时还没有的方法,未来添加进来,也可以走到增强逻辑。
这里的使用例子,大致分成这么几个步骤:
- 定义接口 UserService,并定义2个方法
- 定义接口实现 UserServiceImpl,并实现上述2个方法
- 定义java.lang.reflect.InvocationHandler的一个实现类,在这个实现类中,对方法前后进行增强
- 在调用处使用
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目录下,如下图所示:
$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个方法,其中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属性,我们可以打开这个类看下。
从上面2个图片中,可以看到,这个h是一个InvocationHandler类型的对象,这个值是在Proxy的构造方法中传入的,其实就是在我们调用Proxy.newProxyInstance
的时候传入的myHandler对象,如下图中的源码所示
我的解答
通过上面的示例和源码解读,我们已经大致明白了JDK动态代理的过程,包括使用的方法、自动生成的源码、Proxy.newProxyInstance
方法的调用等。我们回到今天开始的问题:为什么JDK动态代理只能代理接口?
我理解可以从这些方面来解答:
- 动态代理是为了做多个方法的增强,而我们在使用的地方,必须可以获取到代理对象,且可以使用被代理对象的类型来接收,就如果上面我们使用UserService类型的对象proxy来接收这个代理对象,这样我们才能调用原始的方法,否则,我们无法用原始对象接收的话,我们必须使用这个代理对象的类型来接收,但是我们事先是不知道这个自动生成的代理对象的类型的,如果事先知道的话,这里其实就退化成了静态代理。
- 既然要使用原始的类型来接收,那么在java中有2种方法:继承原始对象成为子类,或者 实现原始对象接口。
- JDK动态 代理自动生成对的类
$Proxy0
继承了java.lang.reflect.Proxy
类,由于java是单继承的,所以这里没有机会再去继承被代理类。 - 所以,这里只剩下第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的接口,请在评论区说出你的理解,大家共同讨论提高。