1. JDK 动态代理
JDK 自带的动态代理基于 接口 实现。它主要依赖
java.lang.reflect.Proxy
类和InvocationHandler
接口。
- InvocationHandler:用于定义代理类中方法的处理逻辑。代理对象的方法调用会被重定向到
InvocationHandler
的invoke
方法。- Proxy.newProxyInstance:创建代理对象,接受目标类的类加载器、接口列表、
InvocationHandler
实例作为参数。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Service { void execute(); } class ServiceImpl implements Service { public void execute() { System.out.println("Service is executing..."); } } class ServiceInvocationHandler implements InvocationHandler { private final Object target; public ServiceInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before executing service..."); Object result = method.invoke(target, args); System.out.println("After executing service..."); return result; } } public class Main { public static void main(String[] args) { Service service = new ServiceImpl(); Service proxyInstance = (Service) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), new ServiceInvocationHandler(service) ); proxyInstance.execute(); } }
在这个例子中:
ServiceInvocationHandler
是代理类,实现了InvocationHandler
接口。- 代理对象
proxyInstance
会拦截execute
方法的调用,并执行自定义的前后逻辑。
2. CGLIB 动态代理
CGLIB(Code Generation Library)基于 子类 来实现动态代理。与 JDK 动态代理不同,CGLIB 允许为没有实现接口的类创建代理类。
CGLIB 通过生成目标类的子类,并在子类中拦截方法调用(使用字节码增强技术),添加代理逻辑。Spring AOP 在没有接口的情况下,通常会使用 CGLIB 来生成代理
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; class Service { public void execute() { System.out.println("Service is executing..."); } } class ServiceMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before executing service..."); Object result = proxy.invokeSuper(obj, args); System.out.println("After executing service..."); return result; } } public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service.class); enhancer.setCallback(new ServiceMethodInterceptor()); Service proxy = (Service) enhancer.create(); proxy.execute(); } }
在这个例子中:
ServiceMethodInterceptor
实现了MethodInterceptor
接口,用于拦截方法调用。Enhancer
类用于生成代理对象,并将拦截器应用到代理对象的所有方法。总结
- JDK 动态代理:适用于有接口的类,基于接口实现代理。
- CGLIB 动态代理:适用于无接口的类,基于子类继承实现代理。
在 Spring 中,默认使用 JDK 动态代理。如果目标类没有接口,则自动切换到 CGLIB 动态代理。
什么叫动态代理
动态代理是一种在程序运行时动态生成代理对象的技术。它的核心概念是:通过代理对象拦截方法调用,并在方法执行前后插入额外的操作,比如日志记录、事务管理、权限检查等。
与静态代理不同,动态代理在编译期并不知道代理的对象,而是通过运行时生成的方式实现。这使得动态代理更加灵活,适用于许多不同的类或接口,而不需要在每个类上重复编写代理代码。
动态代理的核心特点
- 动态生成代理对象:不需要提前定义代理类,在运行时生成代理对象。这使得代理可以应用到不同的类或接口。
- 拦截方法调用:代理对象在方法被调用时可以拦截该方法,从而在方法执行前后加入额外的逻辑。
- 对目标对象透明:代理对象和目标对象对外表现出一致的接口,调用者通常不需要知道自己调用的是代理对象还是原始对象。
动态代理的典型实现方式
JDK 动态代理:
- JDK 动态代理只能代理实现了接口的类,因为它通过接口创建代理对象。
- 使用 Java 的
Proxy
类和InvocationHandler
接口来实现。CGLIB 动态代理:
- CGLIB 是基于字节码生成技术实现的,能够代理没有实现接口的类(基于继承的方式生成子类)。
- 使用
Enhancer
类和MethodInterceptor
接口来创建代理对象。为什么使用动态代理?
动态代理的主要目的是分离业务逻辑与通用功能,从而实现面向切面编程(AOP)。例如,日志记录、事务管理、缓存控制等功能往往是应用程序中的通用需求,但将这些逻辑写在业务代码中会造成重复和耦合。动态代理提供了一种方式,在不修改业务代码的情况下实现这些功能,提升代码的清晰性和维护性。
在 Spring 和其他框架中,有许多常用的注解是基于动态代理实现的。这些注解通常用于实现横切关注点(如事务、缓存、异步调用等),通过动态代理拦截方法调用,插入相应的逻辑。以下是一些常见的基于动态代理的注解:
1.
@Transactional
(事务管理)
- 用途:用于方法或类上,用于声明一个方法或类中的方法需要事务管理。
- 实现方式:Spring 使用动态代理在方法执行前开启事务,执行完后提交或回滚事务。
- 代理逻辑:拦截器会在方法执行前开始事务,方法执行后根据结果决定提交还是回滚事务。
2.
@Cacheable
、@CachePut
、@CacheEvict
(缓存管理)
- 用途:用于缓存数据。
@Cacheable
会在方法执行前检查缓存是否存在,如果存在则直接返回缓存中的结果;@CachePut
用于更新缓存;@CacheEvict
用于清除缓存。- 实现方式:使用动态代理在方法执行前后检查或操作缓存,以避免重复计算和加快数据读取。
- 代理逻辑:在方法调用前检查缓存,如果有缓存数据则直接返回结果,否则执行方法并将结果存入缓存。
3.
@Async
(异步调用)
- 用途:用于将某个方法异步执行,即方法在后台执行,调用者不必等待方法完成。
- 实现方式:Spring 使用动态代理将该方法放入线程池异步执行。
- 代理逻辑:拦截方法调用,将方法提交到线程池执行,并返回
Future
对象或者不等待结果。4.
@Scheduled
(任务调度)
- 用途:用于定时执行某个方法。
- 实现方式:Spring 使用动态代理结合线程池或定时任务调度器,在指定的时间或周期执行方法。
- 代理逻辑:将方法注册到调度器中,按指定时间间隔或 cron 表达式执行。
5.
@PreAuthorize
、@PostAuthorize
、@Secured
(权限控制)
- 用途:用于方法级的权限控制,在方法执行前或执行后验证用户权限。
- 实现方式:Spring Security 使用动态代理在方法执行前后检查用户是否有权限执行该操作。
- 代理逻辑:拦截方法调用,根据注解配置的权限规则检查用户权限,若无权限则抛出异常。
6.
@Retryable
(重试机制)
- 用途:用于在方法执行失败时自动重试,通常用于网络请求、数据库连接等不稳定操作。
- 实现方式:Spring Retry 使用动态代理在方法调用失败时进行重试。
- 代理逻辑:拦截方法调用,在失败时按配置的重试规则(重试次数、延迟时间等)重新执行方法。
7.
@RestController
/@Controller
(控制层代理)
- 用途:用于标记 Spring MVC 的控制器,处理 HTTP 请求。
- 实现方式:Spring 使用动态代理生成控制器对象,并管理控制器的生命周期和请求响应处理。
- 代理逻辑:在请求处理前后可以插入过滤器、拦截器等,统一管理请求、响应、异常处理等逻辑。
8.
@Around
(AOP 切面注解)
- 用途:用于定义切面方法,在目标方法执行前后加入额外逻辑。
- 实现方式:Spring AOP 使用动态代理创建代理对象,将切面方法插入目标方法的执行链。
- 代理逻辑:拦截方法调用,将切面逻辑插入到方法前、后,甚至替换方法的实际调用。
总结
这些注解大部分通过动态代理来实现方法的拦截和控制逻辑,避免了在业务代码中混入横切逻辑,使代码更清晰、可维护。在使用这些注解时,Spring 自动生成代理对象并处理复杂的底层实现,我们只需简单的注解配置即可享受代理带来的功能。
记忆已更新
在 Java 开发中,关于动态代理的问题通常围绕其原理、使用场景、优缺点和实现方式展开。以下是一些常见的问题和可能的答案方向:
1. 什么是动态代理?
- 答:动态代理是在运行时创建代理对象的机制。Java 动态代理允许在不修改原始类的情况下,为目标对象添加额外的功能,通常用于拦截方法调用。JDK 动态代理依赖接口,CGLIB 动态代理可以代理类。
2. Java 中有哪些实现动态代理的方式?
- 答:主要有两种方式:
- JDK 动态代理:通过
java.lang.reflect.Proxy
类和InvocationHandler
接口实现,适用于实现了接口的类。- CGLIB 动态代理:基于字节码生成的代理库,通过继承目标类来创建代理对象,适用于没有接口的类。
3. 动态代理的应用场景有哪些?
- 答:动态代理通常用于:
- AOP(面向切面编程):如日志、事务管理、权限控制等。
- RPC 框架:远程调用时拦截方法,实现通信和序列化。
- 代理模式和装饰器模式的实现:动态增加功能而不修改原有代码。
4. JDK 动态代理的原理是什么?
- 答:JDK 动态代理使用
Proxy.newProxyInstance
创建代理对象,需要一个InvocationHandler
实现。每次调用代理对象的方法时,都会转发给InvocationHandler.invoke
方法,通过反射执行目标方法。5. CGLIB 动态代理的原理是什么?
- 答:CGLIB 通过继承目标类并重写其方法实现代理。它基于 ASM 框架操作字节码,因此能代理没有接口的类,但不能代理
final
类和final
方法。6. 动态代理与静态代理有什么区别?
- 答:静态代理需要手动实现代理类,代码量较大,代理逻辑是编译期决定的;动态代理在运行时生成代理类,灵活性高,代码简洁。
7. Spring AOP 是如何实现动态代理的?
- 答:Spring AOP 默认使用 JDK 动态代理,如果目标类没有实现接口,则使用 CGLIB 动态代理。Spring 会为
@Around
、@Before
等切面方法创建代理对象,并在调用链中添加MethodInterceptor
来实现切面逻辑。8. 如何选择 JDK 动态代理和 CGLIB?
- 答:如果目标类实现了接口,优先选择 JDK 动态代理,效率较高;如果没有接口,选择 CGLIB,但要注意不能代理
final
类或方法。9. 可以解释一下
InvocationHandler
的作用吗?
- 答:
InvocationHandler
是 JDK 动态代理的核心接口,每次代理对象的方法被调用时都会调用invoke
方法,开发者可以在invoke
方法中编写额外的逻辑,比如日志、事务管理等。- 总结一下选择规则
没有实现接口的类:只能使用 CGLIB 动态代理。
实现了接口的类:
- 默认使用 JDK 动态代理。
- 如果强制使用 CGLIB(例如,Spring 中设置
proxyTargetClass=true
),那么即使类实现了接口,也会使用 CGLIB。