引言:在面试的过程中,经常会有面试官询问关于动态代理的问题,特别是 JDK 动态代理与 CGLIB 动态代理的区别以及它们各自的应用场景。如果你对动态代理的概念和细节尚未了解,面对这样的问题可能会感到不安。因此,本文旨在深入探讨动态代理的原理和实践,帮助你掌握这一关键知识点,从而在面试中占据有利地位。
题目
动态代理是基于什么原理?
推荐解析
什么是动态代理?
动态代理是一种在运行时动态生成代理对象,并在代理对象上进行方法调用的编程技术。它主要用于在不修改原有代码基础上,增加或改变某些功能的执行流程。动态代理广泛应用于 AOP(面向切面编程)、RPC(远程过程调用)、事务管理等领域。在 Java 中,主要有两种动态代理的实现方式:JDK 动态代理和 CGLIB 动态代理。
JDK 动态代理
JDK 动态代理是基于接口的代理技术。它使用
java.lang.reflect.Proxy
类和
java.lang.reflect.InvocationHandler
接口来创建代理对象。当你调用代理对象的任何方法时,调用会被转发到
InvocationHandler
的
invoke
方法。你可以在这个
invoke
方法中定义拦截逻辑,比如前置处理、后置处理等。
为了使用 JDK 动态代理,你的类必须实现一个或多个接口。JDK 动态代理的局限性在于,它只能代理接口方法,如果你有一个类并希望代理其非接口方法,则不能使用 JDK 动态代理。
优点以及缺点
优点 :原生支持,无需引入额外依赖。
缺点 :只能代理接口,如果一个类没有实现任何接口,则不能使用 JDK 动态代理。
操作步骤
1)定义一个接口及其实现类。
2)创建一个实现了
InvocationHandler
接口的类,在该类的
invoke
方法中定义代理逻辑。
3)通过
Proxy.newProxyInstance
方法动态创建接口的代理对象。
代码示例
// 定义一个接口
publicinterfaceHelloService{
voidsayHello(String name);
}
// 实现该接口的类
public classHelloServiceImplimplementsHelloService{
@Override
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
//创建一个实现 InvocationHandler 接口的类
public classHelloServiceHandlerimplementsInvocationHandler{
//目标对象
private Object target;
publicHelloServiceHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("Before sayHello");
// 执行目标对象的方法
Object result = method.invoke(target, args);
System.out.println("After sayHello");
return result;
}
}
//通过 Proxy.newProxyInstance 方法动态创建接口的代理对象
publicstaticvoidmain(String[] args){
// 创建目标对象
HelloService target = new HelloServiceImpl();
// 创建调用处理器对象
HelloServiceHandler handler = new HelloServiceHandler(target);
// 创建代理对象
HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
target.get class().get classLoader(),
target.get class().getInterfaces(),
handler);
// 通过代理对象调用方法
proxyInstance.sayHello("World");
}
CGLIB 动态代理
CGLIB(Code Generation Library)是一个强大的、高性能、高质量的 Code 生成库,它可以在运行时扩展 Java 类和实现 Java 接口。不同于 JDK 动态代理,CGLIB 不需要接口,它是通过继承方式实现代理的。
CGLIB 底层通过使用一个小而快的字节码处理框架 ASM ,来转换字节码并生成新的类。不仅可以代理普通类的方法,还能代理那些没有接口的类的方法。
优点以及缺点
优点 :无需接口实现。在大量调用的场景下,其生成的代理对象在调用时性能比 JDK 动态代理高
缺点 :对 final 方法无效,需添加额外的依赖。
操作步骤
1)创建一个需要被代理的类。
2)创建一个继承
MethodInterceptor
的代理类,在
intercept
方法中定义代理逻辑。
3)使用
Enhancer
类创建被代理类的子类,并设置回调。
代码示例
<!--引入 cglib依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
//定义一个普通类
public classHelloService{
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
//创建一个继承 MethodInterceptor 的代理类 实现 intercept 方法
public classHelloServiceCglibimplementsMethodInterceptor{
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuper class(this.target.get class());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)throws Throwable {
System.out.println("Before sayHello");
// 执行目标类的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("After sayHello");
return result;
}
}
//实现
publicstaticvoidmain(String[] args){
HelloService target = new HelloService();
HelloServiceCglib cglib = new HelloServiceCglib();
HelloService proxy = (HelloService) cglib.getInstance(target);
proxy.sayHello("World");
}
原理对比
实现方式 :JDK 动态代理通过反射机制调用接口方法,CGLIB 通过生成目标对象的子类并覆盖其方法实现代理。
性能 :在调用方法的次数非常多的情况下,CGLIB 的性能可能优于 JDK 动态代理,但差异通常不大。CGLIB 初始化的代理对象比 JDK 动态代理慢,因为它需要生成新的类。
使用场景 :
如果目标对象实现了接口,推荐使用 JDK 动态代理。
如果目标对象没有实现接口,或者有特定需求需要通过继承方式代理,则使用 CGLIB。
其他补充
鱼聪明 AI 的回答:
鱼聪明 AI 地址:https://www.yucongming.com/
静态代理知识补充
静态代理是一种设计模式,它在程序运行前就已经存在代理类的代码,代理类和目标对象实现相同的接口或继承相同的父类。通过代理类来间接访问目标对象,从而在不修改目标对象代码的情况下,增加或改变某些功能的执行流程。静态代理通常用于控制对目标对象的访问,或在调用目标对象的方法前后添加额外的功能,如安全检查、事务处理、日志记录等。
静态代理的特点
编译时增加功能 :静态代理的实现在编译阶段就已经完成,所有的增强功能都需要在代理类中显式编写。
代码冗余 :如果有多个类需要代理,每个类都需要一个对应的代理类,这会导致大量的重复代码。
紧耦合 :代理类和目标对象之间的关系在编译时就已经确定,增加或修改代理类需要重新编译。
静态代理的实现
静态代理的实现涉及到四个角色:
1) 接口( Interface ) :定义了目标对象和代理对象共同遵循的操作集合。
2) 目标对象( Target Object ) :实现了接口的类,定义了要执行的具体操作。
3) 代理对象( Proxy Object ) :同样实现了接口,用于包装目标对象,可以在调用目标对象的方法前后执行一些附加操作。
4) 客户端( Client ) :使用代理对象的用户。
简单实现
假设有一个简单的接口和实现类,接口定义了一个
sayHello
方法:
// 定义接口
publicinterfaceHelloService{
voidsayHello(String name);
}
// 实现接口的目标类
public classHelloServiceImplimplementsHelloService{
@Override
publicvoidsayHello(String name){
System.out.println("Hello, " + name);
}
}
// 静态代理类 创建一个代理类来增强 sayHello 方法:
public classHelloServiceProxyimplementsHelloService{
private HelloService helloService;
publicHelloServiceProxy(HelloService helloService){
this.helloService = helloService;
}
@Override
publicvoidsayHello(String name){
System.out.println("Before sayHello"); // 前置增强
helloService.sayHello(name);
System.out.println("After sayHello"); // 后置增强
}
}
//实现
publicstaticvoidmain(String[] args){
HelloService helloService = new HelloServiceImpl();
HelloService proxy = new HelloServiceProxy(helloService);
proxy.sayHello("World");
}
推荐文章和书籍
文章:https://zhuanlan.zhihu.com/p/86293659
书籍:【 Java 核心技术卷 I 】
欢迎交流
在阅读完本文后,你应该对 Java 中的动态代理机制,包括 JDK 动态代理和 CGLIB 动态代理,有了深入的理解。掌握了这些代理技术,你一定能在讨论 Java 设计模式、面向切面编程( AOP )以及中间件开发时,与面试官或同行进行深入的交流。本文最后提出三个问题,欢迎大家在评论区分享观点,以促进共同进步:
1)在哪些场景下应该优先考虑使用 JDK 动态代理和 CGLIB 动态代理,以优化程序结构或增强功能?
2)如何合理选择和应用 JDK 动态代理与 CGLIB 动态代理,以构建灵活且高效的 AOP 框架或中间件?
3)JDK 动态代理和 CGLIB 动态代理在性能、适用场景和易用性方面有何不同,这些差异如何影响应用的设计和选择?
这些问题旨在深化对 Java 动态代理技术及其在实际应用中的作用的理解,期待你的参与和贡献。
👇🏻 点击下方阅读原文,获取鱼皮往期编程干货
往期推荐