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

一些回答

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

  • 是JDK动态署理是基于接口完成的,当你运用Proxy类创立署理目标时,你需求指定一个接口列表来表示署理目标所应该完成的接口,这些接口就成为署理目标的类型
  • java是单承继的,JDK动态署理承继了Proxy类,无法同时承继被署理类,只能去完成被署理接口

其实这些答案我了解都是对的,可是都说的比较模糊,没有彻底说透,自己运用idea写了例子,看了下生成的署理类,加深一些了解。

运用例子

在实践的开发中,运用动态署理,目的大多是为了对多个办法增加一致的增强逻辑,且不对原始代码做入侵。这儿强调了多个,由于在我了解,假如仅仅为了对一个办法做增强,运用静态署理也能够做到不对原始代码做入侵,完成相同的作用。咱们运用动态署理,便是为了这儿的动态,对多个办法做一致增强,乃至是暂时还没有的办法,未来增加进来,也能够走到增强逻辑。

这儿的运用例子,大致分红这么几个步骤:

  1. 界说接口 UserService,并界说2个办法
  2. 界说接口完成 UserServiceImpl,并完成上述2个办法
  3. 界说java.lang.reflect.InvocationHandler的一个完成类,在这个完成类中,对办法前后进行增强
  4. 在调用处运用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目录下,如下图所示:

为什么JDK动态代理只能代理接口

$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个办法,其间hashCodeequalstoString应该是原始类从Object承继过来的,getNameByIdgetAllUserNameList是咱们自己界说的办法。在这个类的静态代码块中,对着5个Method目标进行了赋值,供这个类中的5个办法分别运用。这个类的5个办法,核心是调用了super.h.invoke办法。由于这个$Proxy0类是承继自java.lang.reflect.Proxy类,这儿的super.h天然也便是java.lang.reflect.Proxy类的h特点,咱们能够翻开这个类看下。

为什么JDK动态代理只能代理接口

为什么JDK动态代理只能代理接口

从上面2个图片中,能够看到,这个h是一个InvocationHandler类型的目标,这个值是在Proxy的结构办法中传入的,其实便是在咱们调用Proxy.newProxyInstance的时候传入的myHandler目标,如下图中的源码所示

为什么JDK动态代理只能代理接口

我的回答

经过上面的示例和源码解读,咱们现已大致了解了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的接口,请在评论区说出你的了解,我们共同讨论提高。