探究动态代理的秘密

1 代理模式

动态代理属于代理模式,那究竟什么是代理模式呢?

说白了,代理模式就是为对象提供一个代理以控制对某个对象的访问,原对象被称为委托类,这个代理的实现被称为代理类。代理类在为委托类预处理消息之后会将消息转发给委托类,之后还能进行消息的后置处理。代理类不实现具体服务,而是利用委托类来完成服务。

2 代理模式的优势

为啥我们要加一个代理类呢?直接使用委托类来完成业务需求不简单吗?事实上,使用代理模式是有大大的好处滴。

  1. 隐藏了委托类的实现
  2. 实现了客户与委托类之间的解耦
  3. 我们可以在不修改委托类代码的情况下做一些额外的处理

在 Java 的许多场景中我们都会运用到代理模式,举个栗子,在 Spring 的 AOP 切面中我们为切面生成了一个代理类。

代理主要分为静态代理、JDK 动态代理和 CGLIB 动态代理,它们都有各自的特点,接下来我会详细介绍这几种代理模式。

3 静态代理

实现

先建立一个委托接口

//委托接口
public interface WorkInterface {

	void work();
}

实现委托类

//委托类
public class WorkService implements WorkInterface {

	@Override
	public void work() {
		System.out.println("在这里实现相应的业务逻辑");
	}

}

再实现代理类

//代理类
public class WorkProxy implements WorkInterface {
	
	private WorkInterface workInterface = new WorkService();

	@Override
	public void work() {
		System.out.println("在执行业务逻辑之前记录日志");
		workInterface.work();
		System.out.println("在执行业务逻辑之后记录日志");
	}

}

测试一下

//测试类
public class WorkTest {

	public static void main(String[] args) {
		WorkInterface workProxy = new WorkProxy();
		workProxy.work();
	}
}

执行结果是没问题的

在执行业务逻辑之前记录日志
在这里实现相应的业务逻辑
在执行业务逻辑之后记录日志

优点

静态代理实现过程比较简单,其代理关系在编译期间就已经确定。静态代理适用于委托类中委托方法较少的情况下。

缺点

当委托类中委托方法很多时,我们需要在代理类中写一大堆的代理方法,在效率上是难以接受的。

4 浅谈动态代理

动态代理是指代理类在程序运行时创建(静态代理是在编译期创建)。

相信大家之前对 JVM 的类加载机制多多少少有些了解,其主要分为五个阶段,分别为加载,验证,准备,解析,初始化。在加载阶段,JVM 会完成以下三个步骤:

  1. 通过一个类的全名或其它途径来获取这个类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 Class 对象,作为方法区中对这个类访问的入口

动态代理就发生在加载阶段的第一个阶段,类的二进制字节流来源有很多,包括但不限于 zip 包、运行时计算生成、数据库获取等,我们所说的动态代理技术便是运行时计算生成,根据接口或者目标对象计算出代理类的字节码然后加载进 JVM 中。

一般使用动态代理有两种常见的做法,一是通过使用接口的 JDK 动态代理,一是通过使用继承类的 CGLIB 动态代理。

5 JDK 动态代理

实现

实现一个委托接口

//委托接口
public interface WorkInterface {

	void work1();
	
	void work2();
}

接着实现一个委托类

//委托类
public class WorkService implements WorkInterface {

	@Override
	public void work1() {
		System.out.println("在这里实现相应的业务逻辑1");
	}
	
	@Override
	public void work2() {
		System.out.println("在这里实现相应的业务逻辑2");
	}

}

生成一个中间类

//中间类
public class WorkProxyInvocationHandler implements InvocationHandler{

	//持有委托类对象的引用
	private Object obj ;
	
	//传入委托类的对象
	public WorkProxyInvocationHandler(Object obj){
        this.obj = obj;

    }
	
	/**
	 * @param proxy 代理对象
     * @param method 代理方法
     * @param args 方法的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("在执行业务逻辑之前记录日志");
		method.invoke(obj, args);
		System.out.println("在执行业务逻辑之后记录日志");
		return null;
	}
	
	//动态生成代理类对象
	public Object newProxyInstance() {
		return Proxy.newProxyInstance(
                //指定代理对象的类加载器
                obj.getClass().getClassLoader(),
                //代理对象需要实现的接口,可以同时指定多个接口
                obj.getClass().getInterfaces(),
                //方法调用的实际处理者,代理对象的方法调用都会转发到这里
                this);
	}
		
}

最后测试一下

//测试类
public class WorkTest {

	public static void main(String[] args) {
		WorkProxyInvocationHandler workProxyInvocationHandler = new WorkProxyInvocationHandler(new WorkService());
		WorkInterface workService = (WorkInterface) workProxyInvocationHandler.newProxyInstance();
		workService.work1();
		workService.work2();
	}
}

结果如下

在执行业务逻辑之前记录日志
在这里实现相应的业务逻辑1
在执行业务逻辑之后记录日志
在执行业务逻辑之前记录日志
在这里实现相应的业务逻辑2
在执行业务逻辑之后记录日志

原理

我们需要生成一个实现 InvocationHandler 接口的中间类。

public interface InvocationHandler {

    /**
     * @param proxy 代理类对象
     * @param method 标识具体调用的是代理类的哪个方法
     * @param args 代理类方法的参数
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

然后,我们在代理类中的所有方法的调用都会变成对 invoke 方法的调用,因此我们可以在 invoke 方法中添加统一的处理逻辑。

实现了 InvocationHandler 的类被称为中间类,它具有一个委托类对象引用,并在 invoke 方法中调用了委托类对象的相应方法。

我们看一下这行代码

WorkInterface workService = (WorkInterface) workProxyInvocationHandler.newProxyInstance();

在这里我们通过 newProxyInstance 方法获取了一个代理类实例。通过这个代理类的实例,我们可以调用代理类的方法,而对代理类的方法调用都会调用中间类的 invoke 方法,在 invoke 方法中我们会调用委托类的对应方法,同时加上自己的处理逻辑。

中间类与委托类之间构成了静态代理关系,中间类是代理类,委托类是委托类。然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。

从此,我们可以发现,动态代理其实是由由两组静态代理关系组成的。

特点

动态生成的代理类和委托类实现同一个接口,其内部是通过反射机制实现的。

与静态代理相比,当委托类中委托方法很多且代理逻辑相同的情况下只需要实现一个 invoke 方法即可,效率大大提高。

JDK 动态代理为什么只能代理接口?

代理类本身已经继承了 Proxy 这个类,而 java 是不允许多重继承的,但是允许实现多个接口。

6 CGLIB 动态代理

JDK 动态代理的局限性

JDK 动态代理依赖接口实现,如果我们只有类没有接口,就无法使用 JDK 动态代理。这时,我们需要使用另一种动态代理技术 – CGLIB 动态代理。

实现

生成一个委托类

//委托类
public class WorkService {

	public void work1() {
		System.out.println("在这里实现相应的业务逻辑1");
	}
	
	public void work2() {
		System.out.println("在这里实现相应的业务逻辑2");
	}

}

实现 MethodInterceptor 接口

public class WorkInterceptor implements MethodInterceptor {

	//CGLIB增强类对象,代理类对象是由Enhancer类创建的
	private Enhancer enhancer = new Enhancer();
	
	/**
	 * @param obj  被代理的对象
     * @param method 代理的方法
     * @param args 方法的参数
     * @param proxy CGLIB方法代理对象
     * 
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("在执行业务逻辑之前记录日志");
		proxy.invokeSuper(obj, args);
		System.out.println("在执行业务逻辑之后记录日志");
		return null;
	}

	//使用动态代理创建一个代理对象
	public Object newProxyInstance(Class<?> c) {
        //设置产生的代理对象的父类,增强类型
        enhancer.setSuperclass(c);
        //定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor接口
        enhancer.setCallback(this);
        //使用默认无参数的构造函数创建目标对象,这是一个前提,被代理的类要提供无参构造方法
        return enhancer.create();
    }

}

测试一下

//测试类
public class WorkTest {

	public static void main(String[] args) {
		WorkInterceptor workInterceptor = new WorkInterceptor();
		WorkService workService = (WorkService) workInterceptor.newProxyInstance(WorkService.class);
		workService.work1();
		workService.work2();
	}
}

结果:

在执行业务逻辑之前记录日志
在这里实现相应的业务逻辑1
在执行业务逻辑之后记录日志
在执行业务逻辑之前记录日志
在这里实现相应的业务逻辑2
在执行业务逻辑之后记录日志

原理

CGLIB 代理对指定的委托类生成一个子类并重写其中业务方法来实现代理。代理类对象是由 Enhancer 类创建的。CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非 final 的 public 类型的方法
  2. 将这些方法的定义转成字节码
  3. 将组成的字节码转换成相应的代理的 Class 对象然后通过反射获得代理类的实例对象
  4. 实现 MethodInterceptor 接口,用来处理对代理类上所有方法的请求

7 总结

静态代理与动态代理的区别

  1. 静态代理的代理关系在编译期就已经确定,动态代理的代理关系是在运行期确定的
  2. 静态代理实现比较简单,适合于委托类较少且确定的情况,而动态代理的优势在于其灵活性

CGLIB 动态代理与 JDK 动态代理的区别

  1. JDK 动态代理基于接口实现,CGLIB 动态代理基于类实现
  2. JDK 动态代理基于 Java 反射机制实现,CGLIB 动态代理基于 ASM 框架通过生成业务类的子类实现

CGLIB 动态代理与 JDK 动态代理的原理

  1. JDK 动态代理利用反射生成代理类字节码,并生成对象
  2. CGLIB 动态代理采用了非常底层的字节码技术,通过字节码为一个类创建子类,并在子类中采用方法拦截,拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。使用 CGLIB 动态代理需要实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现

参考:Java 静态代理、Java动态代理、CGLIB动态代理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章