面试题 / Java

代理

关于 Java 代理的详细介绍,可以看看笔者写的 Java 代理模式详解这篇文章。

如何实现动态代理?

动态代理是一种非常强大的设计模式,它允许我们在不修改源代码的情况下,对一个类或对象的方法进行功能增强(Enhancement)

在 Java 中,实现动态代理最主流的方式有两种:JDK 动态代理CGLIB 动态代理

第一种:JDK 动态代理

Java 官方提供的,其核心要求是目标类必须实现一个或多个接口。JDK 动态代理在运行时,会利用 Proxy.newProxyInstance() 方法,动态地创建一个实现了这些接口的代理类的实例。这个代理类在内存中生成,你看不到它的 .java.class 文件。

当你调用代理对象的任何一个方法时,这个调用都会被转发到我们提供的一个 InvocationHandler 接口的 invoke 方法中。在 invoke 方法里,我们就可以在调用原始方法(目标方法)之前或之后,加入我们自己的增强逻辑。

第二种:CGLIB 动态代理

CGLIB 是一个第三方的代码生成库。它的原理与 JDK 完全不同,它不要求被代理的类实现接口。它在运行时,动态生成目标类的子类作为代理类(通过 ASM 字节码操作技术)。然后,它会重写父类(也就是被代理类)中所有非 finalprivatestatic 的方法。

当你调用代理对象的任何一个方法时,这个调用会被 CGLIB 的 MethodInterceptor 接口的 intercept 方法拦截。和 InvocationHandlerinvoke 方法一样,我们可以在 intercept 方法里,在调用原始的父类方法之前或之后,加入我们的增强逻辑。

静态代理和动态代理有什么区别?

静态代理和动态代理的核心差异在于 代理关系的确定时机、实现灵活性及维护成本

对比维度静态代理 (Static Proxy)动态代理 (Dynamic Proxy)
代理关系确定时机编译期(编译后生成固定的 .class 字节码文件)运行时(动态生成代理类字节码并加载到 JVM)
实现方式手动编写代理类,需与目标类实现同一接口,一对一绑定无需手动编写代理类,通过 Handler/Interceptor 封装增强逻辑,一对多复用
接口依赖必须实现接口(代理类与目标类遵循同一接口规范)支持代理接口或直接代理实现类
代码量与维护性代码量大(目标类越多,代理类越多),维护成本高;接口新增方法时,目标类与代理类需同步修改代码量极少(通用增强逻辑可复用),维护性好;与接口解耦,接口变更不影响代理逻辑
核心优势实现简单、逻辑直观,无额外框架依赖灵活性强、复用性高,降低重复编码,适配复杂场景
典型应用场景简单的装饰器模式、少量固定类的增强需求Spring AOP、RPC 框架(如 Dubbo)、ORM 框架

⭐️JDK 动态代理和 CGLIB 动态代理有什么区别?

  1. JDK 动态代理是官方的,它要求被代理的类必须实现接口。它的原理是动态生成一个接口的实现类来作为代理。CGLIB 是第三方的,它不需要接口。它的原理是动态生成一个被代理类的子类来作为代理。但也正因为是继承,所以它不能代理 final 的类,被代理的方法也不能是 finalprivate
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

⭐️介绍一下动态代理在框架中的实际应用场景

动态代理最典型的应用场景就是Spring AOP

AOP(Aspect-Oriented Programming

)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

SpringAOPProcess


来源引用