近期在预备找一些新的作业时机,在网上看了一些面试常见问题,看看自己是否能比较好的回答。今日的这个问题:为什么JDK动态署理只能署理接口。这个问题看到次数挺多的,所以自己也深入研究了一下。
一些回答
在网上看了很多人的回答,大致有这么一些:
- 是JDK动态署理是基于接口完成的,当你运用
Proxy
类创立署理目标时,你需求指定一个接口列表来表示署理目标所应该完成的接口,这些接口就成为署理目标的类型 - java是单承继的,JDK动态署理承继了
Proxy
类,无法同时承继被署理类,只能去完成被署理接口
其实这些答案我了解都是对的,可是都说的比较模糊,没有彻底说透,自己运用idea写了例子,看了下生成的署理类,加深一些了解。
运用例子
在实践的开发中,运用动态署理,目的大多是为了对多个
办法增加一致的增强逻辑,且不对原始代码做入侵。这儿强调了多个
,由于在我了解,假如仅仅为了对一个办法做增强,运用静态署理也能够做到不对原始代码做入侵,完成相同的作用。咱们运用动态署理,便是为了这儿的动态
,对多个
办法做一致增强,乃至是暂时还没有的办法,未来增加进来,也能够走到增强逻辑。
这儿的运用例子,大致分红这么几个步骤:
- 界说接口 UserService,并界说2个办法
- 界说接口完成 UserServiceImpl,并完成上述2个办法
- 界说java.lang.reflect.InvocationHandler的一个完成类,在这个完成类中,对办法前后进行增强
- 在调用处运用
Proxy.newProxyInstance
来创立署理类,调用UserService接口中的2个办法
下面是这些实践的代码:
UserService
package cn.pdf;
import java.util.List;
public interface UserService {
/**
* 根据用户ID获取用户名字
* @param id 用户id
* @return 用户名字
*/
String getNameById(Long id);
/**
* 获取一切的用户名列表
* @return 一切的用户名列表
*/
List<String> getAllUserNameList();
}
UserServiceImpl
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
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
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类的源码
//
// 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的接口,请在评论区说出你的了解,我们共同讨论提高。