代理模式
1、什么是代理模式
代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的访问引用。它是一种对象结构型模式。在代理模式中,一个类代表另一个类的功能,我们创建具有现有对象的对象,以便向外界提供功能接口。
生活中就有代理模式的例子,比如朋友圈中卖面膜的微商同学。她们从厂家拿货,融合在朋友圈中宣传出售。理论上,顾客可以直接从厂家拿货、购买商品,但是现实生活中往往很少这样。一般都是厂家委托给代理商进行销售,顾客跟代理商进行打交道,而不用直接与产品实际生产者进行关联。
代理模式常见的UML示意图如下:
2、代理模式的特性
(1)意图:
为其他对象提供一种代理以控制对这个对象的访问。
(2)主要解决:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
(3)需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
- 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
- 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
- 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
3、代理模式的优缺点及其应用场景
(1)优点:
1)中介隔离作用:
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
2)开闭原则,增加功能:
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
(2)缺点:
1)由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2)实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
(3)使用场景:
按职责来划分,通常有以下使用场景:
1)远程代理。
2)虚拟代理。
3)Copy-on-Write 代理。
4)保护(Protect or Access)代理。
5)Cache代理。
6)防火墙(Firewall)代理。
7)同步化(Synchronization)代理。
8)智能引用(Smart Reference)代理。
4、代理模式的三种实现方式
代理按照创建时期可以分为静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。其中,在Java中,动态代理又主要有JDK动态代理和Cglib动态代理。
4.1 静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
下面以一个例子来说明:
(1)场景说明:模拟代理用户保存动作,定义一个保存动作的接口:IUserDao.java,然后目标对象实现这个接口的方法UserDao.java,此时如果使用静态代理方式,就需要在代理对象(UserDaoProxy.java)中也实现IUserDao接口,调用的时候通过调用代理对象的方法来调用目标对象。
(2)示例代码如下:
接口及目标对象、代理对象代码分别如下:
//接口
interface IUserDao{
void save();
}
//目标对象
class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("已经保存对象数据....");
}
}
//代理对象
class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target = target;
}
@Override
public void save() {
System.out.println("保存对象数据之前的一系列操作....");
target.save(); //执行目标对象的方法
System.out.println("保存对象数据之后的一系列操作....");
}
}
测试示例:
public class StaticProxyTest {
public static void main(String[] args) {
//测试静态代理
//目标对象
UserDao target = new UserDao();
//代理对象,将目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
//执行的是代理的方法
proxy.save();
}
}
测试结果:
保存对象数据之前的一系列操作....
已经保存对象数据....
保存对象数据之后的一系列操作....
可以看出,我们通过代理对象执行了目标对象的方法,并可以进行相应的动作增强。
静态代理总结:
1.优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展;
2.缺点: 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
4.2 动态代理
既然也是代理,那么动态代理和静态代理的功能和目的是没有区别的。有区别的是动态与静态的区别:在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
动态代理有以下特点:
(1)代理对象,不需要实现接口;
(2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型);
(3)动态代理也叫做:JDK代理,接口代理。
4.2.1 JDK动态代理
类似于上面的场景需求,进行示例说明。
(1)场景说明:
接口类IDUserDao以及接口实现类目标对象DUserDao是一样的,没有做修改.在这个基础上,增加一个代理工厂类(ProxyFactory),
将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后使用代理对象的中同名方法。
(2)示例代码:
接口及代理目标对象:
/接口
interface IDUserDao{
void save();
}
//目标对象
class DUserDao implements IDUserDao{
@Override
public void save() {
System.out.println("已经保存对象数据....");
}
}
代理工厂类:
/**
* 创建动态代理对象
* 动态代理对象不需要实现接口,但是需要指定接口类型
*/
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("JDK动态代.业务执行之前的一系列操作...");
Object returnValue = method.invoke(target,args);
System.out.println("JDK动态代理..业务执行之后的一系列操作...");
return returnValue;
}
}
);
}
}
生成代理对象方法说明:
(1)使用JDK动态代理生成代理对象需要使用JDK中生成代理对象的API,代理类所在的包为:java.lang.reflect.Proxy(在JDK的反射相关的包中)。
(2)JDK实现代理只需要使用newProxyInstance方法生成所需的代理对象即可。该方法声明如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,InvocationHandler h){}
注意该方法时静态方法,且该方法所需的三个参数依次为:
1)ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的;
2)Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型;
3)InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。(我们在此对象的invoke方法中调用目标对象的方法,并据需增加代码操作以增强该操作)。
InvocationHandler接口说明:
1)InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
2)InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
proxy 代理对象
method 代理对象调用的方法
args 调用的方法中的参数
因为Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
测试示例如下:
public class JdkDynamicProxy {
public static void main(String[] args) {
//测试动态代理
IDUserDao target = new DUserDao();
//打印原始类型
System.out.println(target.getClass());
//给目标对象,创建代理对象
IDUserDao proxy = (IDUserDao)new ProxyFactory(target).getProxyInstance();
//内存中生成的动态代理对象
System.out.println(proxy.getClass());
proxy.save();
}
}
测试结果:程序在运行期生成看代理对象,代理对象调用了目标对象的方法并可以实现增加相应的扩展的操作。
class feature.proxy.DUserDao
class feature.proxy.$Proxy0
JDK动态代.业务执行之前的一系列操作...
已经保存对象数据....
JDK动态代理..业务执行之后的一系列操作...
总结:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
4.2.2 Cglib动态代理
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
Cglib动态代理:
(1)上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。 Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
(2)Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为它们提供方法的interception(拦截)。Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
(3)Cglib子类代理实现方法:
1)需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-xxx.jar即可。
2)引入功能包后,就可以在内存中动态构建子类;
3)代理的类不能为final,否则报错;
4)目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
示例代码如下:
目标对象:
//目标对象,没有实现任何接口
class CglibUserDao{
public void save(){
System.out.println("cglib...已保存数据");
}
}
cglib代理工厂:主要是实现了MethodInterceptor接口,实现了器intercept方法,并定义了代理对象(目标对象子类)的生成。
//cglib代理工厂:子类代理工厂,在内存东动态构建一个子类对象
class CglibProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public CglibProxyFactory(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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("cglib..在保存数据之前的增强操作...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("cglib..在保存数据之后的增强操作...");
return returnValue;
}
}
测试示例:
public class CglibDynamicProxy {
public static void main(String[] args) {
//目标对象
CglibUserDao target = new CglibUserDao();
//代理对象
CglibUserDao proxy = (CglibUserDao)new CglibProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
测试结果:
cglib..在保存数据之前的增强操作...
cglib...已保存数据
cglib..在保存数据之后的增强操作...
Cglib代理总结:
Cglib创建的动态代理对象比JDK创建的动态代理对象的性能相差不多,对于实现接口的类,我们可以使用JDK动态代理实现其增强;对于没有实现接口的类,我门就可以通过cglib动态代理来实现其增强。
4.3 总结
(1)代理分为静态代理和动态代理两种。
(2)静态代理,代理类需要自己编写代码写成。
(3)动态代理,代理类通过 Proxy.newInstance() 方法生成。
(4)不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
(5)静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
(6)动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
(7)代理模式本质上的目的是为了增强现有代码的功能。
5、代理模式的典型应用示例
(1)Spring AOP面向切面编程就是通过动态代理来实现的;
(2)MyBatis等框架都是代理模式的典型应用。
本文源代码:
https://github.com/JianfuYang/2020-yjf-review/tree/master/src/designpatterns/proxy
声明:本文部分内容整理来源于网络,仅做个人学习使用!侵删~