写在前面

相信每当想起有关动态代理的时候大家都会脱口而出的就是:cglib动态 和 JDK 动态代理。

再细一点的话也就是 cglib 动态代理底层使用的是继承,JDK 动态代理使用的实现。

那么,为什么 JDK 动态代理一定要是实现接口的形式?使用继承不行吗?

不知道诸君有没有想过这个问题?

下面就开始解答该问题:

准备环境

为了解答该问题我们首先来构建一个程序复现该问题。

现在就基于 spring V5.0.7.RELEASE 构建一个 maven 实例,项目名称随意!

包名:com.mingrn.proxy

pom 文件中引入如下依赖:

  1. <dependencyManagement>
  2. <dependencies>
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-framework-bom</artifactId>
  6. <version>5.0.7.RELEASE</version>
  7. <type>pom</type>
  8. <scope>import</scope>
  9. </dependency>
  10. </dependencies>
  11. </dependencyManagement>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework</groupId>
  15. <artifactId>spring-context</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework</groupId>
  19. <artifactId>spring-aop</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-aspects</artifactId>
  24. </dependency>
  25. </dependencies>

现在来看下项目结构:

  1. .
  2. └── com.mingrn.proxy
  3. ├── App.java
  4. ├── aop
  5. └── AspectProxy.java
  6. ├── config
  7. └── AppConfig.java
  8. └── service
  9. ├── UserService.java
  10. └── impl
  11. └── UserServiceImpl.java
  • UserService:
  1. public interface UserService {
  2. List find();
  3. }
  • UserServiceImpl:
  1. @Service("userService")
  2. public class UserServiceImpl implements UserService {
  3. @Override
  4. public List find() {
  5. System.out.println("find");
  6. return null;
  7. }
  8. }
  • AppConfig:
  1. @Configuration
  2. @ComponentScan("com.mingrn.proxy")
  3. @EnableAspectJAutoProxy(proxyTargetClass = false)
  4. public class AppConfig {
  5. }
  • AspectProxy:
  1. @Component
  2. @Aspect
  3. public class AspectProxy {
  4. @Pointcut("execution(* *com.mingrn.proxy.service.*.*(..))")
  5. public void pointCut() {
  6. }
  7. @Before("pointCut()")
  8. public void before() {
  9. System.out.println("before");
  10. }
  11. }

现在我们再 App.java 中编写测试,保证 AOP 正常运行:

  1. public class App {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  4. UserService userService = (UserService) context.getBean("userService");
  5. userService.find();
  6. }
  7. }

输出结果如下,表示我们的 AOP 没什么问题!

  1. before
  2. find

现在开始进行问题复现:

复现问题

我们知道,SpringAOP 默认使用的是 JDK 动态代理,那么来看下下面这条输出:

  1. UserService userService = (UserService) context.getBean("userService");
  2. System.out.println("userService instanceof UserServiceImpl ? " + (userService instanceof UserServiceImpl));

为了演示需要,特意在 UserServiceImpl 上为 bean 定义了一个名称,如果直接使用 context.getBean(UserServiceImpl.class); 根据类型获取会出现启动报错问题。

不知道诸君觉得输出的是 true 还是 false ?答案利索当然的是 false 了,对不对。因为 JDK 动态代理会为目标对象在内存中生成一个新的实现代理类,这个新的代理类跟我们源代码中的 UserServiceImpl.java 没有任何关系,唯一的关系是:都实现了 **UserService** 接口类

那么,将 AppConfig 的代理设置为 true@EnableAspectJAutoProxy(proxyTargetClass = true)

现在再次打印相信都知道结果为 true 了,原因就是生成的代理对象与源代码中的 UserServiceImpl.java 是父子关系。

现在我们代理还原为 false@EnableAspectJAutoProxy(proxyTargetClass = false) 再看下如下输出:

  1. UserService userService = (UserService) context.getBean("userService");
  2. System.out.println("userService instanceof Proxy ? " + (userService instanceof Proxy));

诸君现在知道输出结果是什么吗?如果对 JDK 代理有些了解的话就会知道输出结果为:TRUE。那么,为什么会这样?知道该问题后我们自然而然的就知道了:为什么 JDK 动态代理一定是代理接口类!

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

接着上面,不知道诸君有没有看过 JDK 动态代理的源码,如果看过了就应该知道 java.lang.reflect.Proxy 类中有一个内部类 ProxyClassFactory。该内部类有如下一个方法:

  1. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {}

在该方法中调用了 sun.misc.ProxyGenerator 类的如下方法:

  1. public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2){}

即代码如下:

  1. /**
  2. * Generate the specified proxy class.
  3. */
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

这条代码就是为目标类在内存中生成一个代理类,可以看到返回的类型是 byte[]。所以,现在要做的就是利用该语句为 UserService 生成一个代理对象,并将二进制数据生成为一个 class 文件!我们只需要利用反编译工具查看一个该代码即可一幕了然了。

现在开始:

  1. public class App {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  4. UserService userService = (UserService) context.getBean("userService");
  5. Class<?>[] interfaces = new Class[]{UserService.class};
  6. byte[] bytes = ProxyGenerator.generateProxyClass("UserService", interfaces);
  7. File file = new File("/<path>/UserService.class");
  8. try {
  9. OutputStream outputStream = new FileOutputStream(file);
  10. outputStream.write(bytes);
  11. outputStream.flush();
  12. outputStream.close();
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

最后,就会在我们的磁盘中生成一个 UserService.class 字节码文件,我们只需要反编译即可(可以直接利用 IDE 查看,如 IntelliJ IDEA)。打开字节码文件(省略无关内容)如下所示:

  1. public final class UserService extends Proxy implements com.mingrn.proxy.service.UserService {
  2. private static Method m1;
  3. private static Method m3;
  4. private static Method m2;
  5. private static Method m0;
  6. public UserService(InvocationHandler var1) throws {
  7. super(var1);
  8. }
  9. public final boolean equals(Object var1) throws {
  10. // ...
  11. }
  12. public final List find() throws {
  13. // ...
  14. }
  15. public final String toString() throws {
  16. // ...
  17. }
  18. public final int hashCode() throws {
  19. // ...
  20. }
  21. static {
  22. try {
  23. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  24. m3 = Class.forName("com.mingrn.proxy.service.UserService").getMethod("find");
  25. m2 = Class.forName("java.lang.Object").getMethod("toString");
  26. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  27. } catch (NoSuchMethodException var2) {
  28. throw new NoSuchMethodError(var2.getMessage());
  29. } catch (ClassNotFoundException var3) {
  30. throw new NoClassDefFoundError(var3.getMessage());
  31. }
  32. }
  33. }

我们需要关心的仅仅是生成的 UserService 类的继承与实现关系即可:

  1. class UserService extends Proxy implements com.mingrn.proxy.service.UserService {}

现在明白为什么 JDK 动态代理一定是只能为接口生成代理类而不是使用继承了吗?

总结一句话

JDK 动态代理是为接口生成代理对象,该代理对象继承了 JAVA 标准类库 Proxy.java 类并且实现了目标对象。由于 JAVA 遵循单继承多实现原则所以 JDK 无法利用继承来为目标对象生产代理对象。

最后,以后如果再由面试官问题该问题即可将上面这句话砸他脸上,狠狠的那种!

完结,撒花~