我最近在系统整理一些 Java 后台方面的面试题和参考答案,有找工作需求的童鞋,欢迎关注我的 Github 仓库,如果觉得不错可以点个 star 关注 :
前言
今天这篇文章的重点是对 AOP 中动态代理的两种实现方式JDK动态代理
和 CGLIB
做一个简单的探讨和学习,而不会具体地深入到 JDK 动态代理的底层实现中,也不会深入到 CGLIB 的底层实现中。由于篇幅有限,要深入研究 JDK动态代理的实现,就得死磕 Java 反射,而要深入研究 CGLIB 就得死磕它的底层实现 ASM,就得深入去死磕 Java 字节码了,没有个长篇大论是说不清楚的。
什么是静态代理
为了更好地学习动态代理,我们首先了解一下什么是静态代理。这里举个例子,假设有一学生小 A 平时比较懒,作业也不做,但是老师又要检查作业,怕万一每次都不交作业会挂科,这可怎么办呢?很简单,于是他想到了一个听起来还“不错”的办法,自己不做没关系啊,可以花点钱找其他人代做啊,别人赚了点小钱,而小 A 又可以完美地交付了作业,而老师还丝毫无法察觉出来,简直就是两全其美的事儿啊,何乐而不为呢?那么,在这个场景中,如果我们要使用静态代理方式的话,应该要怎么做呢?
- 定义一个学生类的接口
- 实现真正的学生类(目标类)
- 实现代理类
根据以上的准备工作,实现的具体步骤如下:
- 接口
public interface Student {
void doHomeWork();
}
- 目标类(实际操作对象)
public class StudentA implements Student {
@Override
public void doHomeWork() {
system.out.println("RealStudentA doHomeWork.");
}
}
- 代理类
public class StudentProxy implements Student {
private Student student;
public StudentProxy(Student student) {
this.student = student;
}
@Override
public void doHomeWork() {
System.out.println("begin-----------");
student.doHomeWork();
System.out.println("end-----------");
}
}
- 使用
public class ProxyTest() {
public static void main(String[] args) {
StudentA stu = new StudentA();
StudentProxy proxy = new StudentProxy(stu);
proxy.doHomeWork();
}
}
根据代理模式,我们实现了如上所述的一个基本的静态代理的例子,其中,我们提供了一个学生的接口 Student
,而真正的目标类则是 StudentA
,另外还提供了一个代理类StudentProxy
,它会将所有的接口调用都转发给实际的对象,并从实际的对象中获取结果。这样,在实例化代理类 StudentProxy 并提供实际的对象(StudentA 的对象)时,就可以达成小 A 偷懒目的了。
什么是动态代理
我们都知道,学习过 Spring 的话,肯定都少不了要与 Spring AOP 打交道,毕竟 Spring Ioc 和 Spring AOP 是 Spring 最核心的两大功能模块。而 Spring AOP 中,最核心的技术就是动态代理
,其中,主要有两种动态代理的实现方式,一种是 JDK动态代理,一种则是 CGLIB 动态代理。那么,动态代理到底是什么呢?
在回答这个问题之前,我们需要搞清楚一个概念,就是无论是动态代理还是静态代理,本质都是代理模式的一种实现方式而已。那么,什么是代理模式呢?所谓代理模式,即给某一个对象提供一个代理,然后由代理对象控制对目标对象(原对象)的引用
。
现在再回过头来回答一下什么是动态代理呢?动态代理无非就是在运行时能够动态地生成类,并且作为一个真实对象的代理来做事情。这样做的好处就是能够用声明的方式来编程了。
JDK 动态代理与 CGLIB 的区别
Spring AOP 中有两种不同的方式实现动态代理,一种是 JDK 原生的动态代理;另一种是 CGLIB 动态代理。那么,这两种实现方式有什么不同呢?
1、 底层实现的差异:JDK 动态代理基于 Java 反射机制实现,而 CGLIB 则基于 ASM 实现;
2、 功能上的差异:
- JDK 动态代理只能通过接口实现代理(目标类需要实现一个接口,然后由代理类实现);CGLIB 通过继承的方式实现代理,可以通过子类创建代理,也就是说,在这种情况下,代理类称为目标类的子类,而不需要去实现接口。【注】目标类为非 final 类时才可以,final 类不支持继承。
- JDK 动态代理无法转化为原始目标类,因为它只是一个恰好实现了与目标类相同的接口的动态代理而已;
3、 性能上的差异:总体而言,反射机制在生成类的过程中比较高效,而 ASM 在生成类之后的相关执行过程中则比较高效。当然,为了解决 ASM 生成类过程比较低效的问题,我们可以将 ASM 生成的类进行缓存。
AspectJ 静态代理
AspectJ 是一种静态代理的方式,它会直接以 Java 字节码的形式编译到 Java 类中,Java 虚拟机可以向平常一样加载 Java 类运行(因为编译完成的 AspectJ 是完全符合 Java 类的规范的),不会对这个系统的运行带来任何损失。但是,这种静态的方式缺点也很明显,就是灵活性不够。如果横切关注点需要改变织入到系统中的位置,就得重新修改 AspectJ 定义文件,时候使用编译器重新编译 AspectJ 并重新织入到系统中。
而要使用 AspectJ 方式的静态代理,通常,我们可以在配置文件中添加 aop:config 或 aop:spectj-autoproxy,淡然,也可以通过编程的方式创建增强目标对象的代理(注解方式)。下面举个简单的小例子:
// 创建一个可以为给定目标对象生成代理的工厂
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// 添加一个切面,该类必须是一个 @AspectJ 切面
// 可以根据需要多次调用这个方法
factory.addAspect(SecurityManager.class);
// 可以添加现有的切面实例,提供的对象类型必须是 @AspectJ 切面
factory.addAspect(usageTracker);
// 获取代理对象
MyInterfaceType proxy = factory.getProxy();
为什么要使用动态代理
在这篇文章中,提到了两种不同的代理方式静态代理
和动态代理
,既然都有静态代理了,明明可以通过静态代理就可以实现相应功能,我们为啥还要使用动态代理呢?我认为相比于静态代理,动态代理可能由以下几点有点值得我们去用:
- 静态代理不够灵活,而动态代理则停工了更强的灵活性。动态代理的实现支持我们在运行时动态地生成类,而不用在设计实现的时候就指定一个代理类来代理被代理对象;
- 使用动态代理可以在不改变源码的情况下,直接在方法中插入自定义逻辑(切面)。
- 动态代理更加简洁,同时更加解耦,通过参数就可以判断目标类,不需要事先实例化。
总结
本文简要介绍了静态代理,然后着重对动态代理的知识做了一个阐述,并从功能、性能等不同方面了总结了动态代理的两种实现方式的区别,接着简单提及了 AspectJ 静态代理,并给出了两种动态代理的示例。
参考来源:
1、What is the difference between JDK dynamic proxy and CGLib?
2、Explore the Dynamic Proxy API
3、Java 动态代理作用是什么?
4、Java 动态代理(Dynamic proxy) 小结
5、Java Reflection - Dynamic Proxies
6、10分钟看懂动态代理设计模式
7、Java JDK代理、CGLIB、AspectJ代理分析比较
8、AOP的底层实现-CGLIB动态代理和JDK动态代理