概述
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
策略模式旨在不改变被代理对象的基础上进行方法增强。
当我们一个原有的类不能满足功能,又处于职责或其他原因不便修改的时候,我们可以使用代理模式。通过代理类去调用被代理类的方法,在原方法的基础上进行各种加强,又不会改变原代码。
spring 中的 AOP 就运用了代理模式。
一、静态代理
为了简单的说明代理模式,我们举个例子:
首先有一个接口,叫做购房者,然后这个类地下有一个实现类,叫做小明:
1 | //购房者接口 |
买房子还要货比三家,小明没时间,于是就把买房子这件事情委托给中介
1 | public class Intermediary implements Buyers { |
在这个例子里:
- 被代理对象是小明这个类,是我们最终要访问的;
- 代理对象就是中介这个类,他是被代理对象的加强,我们通过代理对象去访问目标对象,在这基础上对其进行加强。
为了做到代理的效果,代理对象和目标对象需要实现同一个接口或者继承同一个类,以达到通过代理对象的方法去调用被代理对象的方法。这种方法叫做静态代理。
二、动态代理
通过静态代理,我们可以在不改变代理类代码的基础上进行方法增强,但是如果有很多这样的代理行为,那么就需要很多代理类或者代理类去实现很多接口的方法,这样做显然会来带麻烦,为此,我们可以选择只在需要的时候才生产代理对象,这就是动态代理。
针对动态代理,有两种实现方法:
- JDK 代理,通过 JDK 的 API 实现;
- CGLib 代理,通过 CGLib 包实现。
1. JDK代理
JDK 代理仍然需要被代理去实现某个接口,因为 JDK 代理的方式本质仍然还是生成一个与被代理类继承同一个接口的类,去代替被代理类的方法提供调用,但是和静态代理不同,这个代理对象由代码在调用的时候动态生成。
我们可以先看看实现代理效果的核心方法Proxy.newProxyInstance()
的注释:
返回指定接口的代理类的实例,该实例将方法调用分派到指定的调用处理程序。
参数:
ClassLoader loader
:指定加载被代理对象的类加载器
Class<?>[] interfaces
:被代理对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h
:事件处理,执行被代理对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
我们需要提供三个参数,被代理类的类的加载器,被代理类实现的结构,一个代理方法要实现的函数式接口。
对于函数式接口 InvocationHandler
,我们不妨再看看他的参数:
proxy
:在方法上调用方法的代理实例method-与在代理实例上调用的接口方法相对应的Method实例。
Method
:对象的声明类将是在其中声明该方法的接口,它可能是代理类通过其继承该方法的代理接口的超接口。
args
:包含在代理实例的方法调用中传递的参数值的对象数组;如果接口方法不带参数,则为null。基本类型的参数包装在适当的基本包装器类的实例中,例如java.lang.Integer或java.lang.Boolean。
可以说非常直观了,下面我们来简单实现一下:
1 | public static void main( String[] args ) { |
2. CGLib 代理
对于动态代理,我们可以通过JDK的Proxy.newProxyInstance()
方法动态的生成一个实现了被代理类指定接口的代理类,那么,如果被代理类没有实现任何接口,那我们该怎么办呢?答案就是使用 CGLib 代理。
CGLib 代理的原理就是在内存里创建一个被代理类的对象的子类,然后重写父类要加强的方法,而无需加强的方法就直接使用父类方法。
要使用 CGLib 代理,需要引入 jar 包 ( Spring 的动态代理就是基于 CGLib 实现的,所以 Spring 的依赖里默认就会引入)
1 | <!-- https://mvnrepository.com/artifact/cglib/cglib --> |
然后我们实现一下:
1 | XiaoMing xiaoMing = new XiaoMing(); |
一般为了方便使用,我们会将代理过程整合到一个代理工厂类中。
由于需要生成子类,所以要被代理的类最好不能用 final 修饰
如何选择代理模式
- JDK 在创建代理对象时的性能要高于 CGLib 代理,而生成代理对象的运行性能却比 CGLib 的低。
- 如果是单例的代理,推荐使用 CGLib
也就是说,如果需要频繁的创建代理对象(多例),那么就该选择JDK代理,否则(单例)使用 CGLib 代理
四、总结
对于两种代理模式:
- 静态代理:被代理类和代理类需要实现相同接口,通过组合的方式,在代理类实例化时将被代理类作为参数传入;
- 动态代理:通过 JDK 自带的
Proxy.newProxyInstance
,在需要的时候动态生成代理类,解决了静态代理需要创建大量代理类的缺点; - CGLib 代理:通过 CGLib 动态生成被代理类的子类来实现代理,解决了动态代理还需要被代理类去实现接口的缺点。
对于动态代理中 JDK 代理和 CGLib 代理的选择:
- 需要频繁的创建代理对象选择 JDK 代理