提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
动态代理(JDK,CGlib)
前言
在介绍动态代理之前,我们需要明白什么是代理。我们可以这样去理解,假设老师需要收取班费,如果老师一个一个学生去收取,是不是就很浪费时间。现实中,老师一般是怎做的了?他会说:同学们先将班费交给班长,班长再交给我,这样老师只需要收取班长一个人的钱。班长在这件事中的角色就是代理,他代理了所有同学进行班费的缴纳
一、静态代理
我们在讲解动态代理之前,先描述一下静态代理,帮助我们去理解这一概念。静态代理实现的前提与JDK动态代理的前提一致,需要有接口。
首先我们定一个接口Person,该接口中只有一个方法,:
public interface Person {
void giveMoney();
}
然后我们定义学生类(实现Person),学生类的主要方法就是交班费:
public class Student implements Person{
private String name;
public Student(String name) {
this.name = name;
}
//交班费
@Override
public void giveMoney() {
System.out.println(name + "上交班费8元");
}
public String getName() {
return name;
}
}
这时候我们的班长出场了(实现Person,作为学生代理StudentProxy去收取班费),StudentProxy中需要持有一个学生实例:
public class StudentProxy implements Person{
private student student;
public StudentProxy(Person student) {
if(student.getClass() == Student.class){
this.student = (Student) student;
}
}
@Override
public void giveMoney() {
//在初始的方法前,我们加一个输出。
System.out.println(student.getName()+"学习进步了");
student.giveMoney();
}
}
测试我们的班长能不能收取到班费:
public class StaticProxyTest {
public static void main(String[] args) {
Student zhangsan = new Student("王五");
Person monitor = new StudentProxy(zhangsan);
monitor.giveMoney();
}
}
//运行结果
王五学习进步了
王五上交班费8元
我们可以看到,班长成功收取到了班费。也许有人要问了,费了这么半天劲,就为了收到一句“王五上交班费8元”,这不是费拉里不讨好吗?不知道有没有有注意到我们在输出“王五上交班费8元”之前,还输出了“王五学习进步了”这么一句话。我们对出事的giveMoney()进行了扩展。这就是代理的好处,我们不需要修改Student类中的任何语句,就可以实现对方法的扩展。这一思想被称为AOP面向切面编程。可让我们对业务代码进行扩展。
二、JDK动态代理
静态代理虽然可以用,但是针对每一个类,比如我们使用的Student类,都需要重新写一个代理类。这就很麻烦,动态代理就解决这一问题。
JDK动态代理,也是需要一个代理对象(班长)去帮助收取班费,但是我们不用自己创建。java.lang.reflect包下的Proxy.newProxyInstance()方法会返回一个创建好的代理对象。我们只需要输入被代理对象的类加载器,接口,以及重写的invoke()方法的InvocationHandler实例。
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),// 代理需要实现的接口,可指定多个,这是一个数组
new InvocationHandler() {// 代理对象处理器
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" + method.getName() + "方法");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("代理方法" + method.getName() + "结束");
return returnValue;
}
}
);
}
}
public class App {
public static void main(String[] args) {
// 目标对象
Person target = new Student("王五");
// 【原始的类型 class dynamic.Student】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
Person proxy = (Person) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.giveMoney();
}
}
//运行结果
class org.proxy.dynamic.Student
class com.sun.proxy.$Proxy0
代理执行giveMoney方法
王五上交班费8块
代理方法giveMoney结束
当我们的代理类调用giveMoney()方法的时候,实际上就是调用invoke()方法。我们在invoke方法中对初始方法进行扩展。
三、CGLib(Code Generation Library)动态代理
CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理执行" + method.getName() + "方法");
//执行目标对象的方法
Object returnValue = method.invoke(target, objects);
System.out.println("代理方法" + method.getName() + "结束");
return returnValue;
}
}
public class App {
@Test
public void test(){
//目标对象
Student target = new Student("李四");
//代理对象
Student proxy = (Student)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.giveMoney();
}
}
//运行结果
代理执行giveMoney方法
李四上交班费8块
代理方法giveMoney结束
两种动态代理的区别
JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,我们只能使用CGLib。
JDK和CGLib动态代理区别
1、JDK动态代理具体实现原理:
- 通过实现InvocationHandler接口创建自己的调用处理器;
- 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象invocationHandler实例作为参数参入
- JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理:
- 利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
3、两者对比:
- JDK动态代理是面向接口的。
- CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。
- CGLib动态代理无法对被final修饰的方法进行扩展。