zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

漫谈模式之代理模式

代理模式 漫谈
2023-06-13 09:17:29 时间

今天,我们来分享结构型模式的另外一个成员:代理模式

代理模式是一种结构型模式,它为其它对象提供一种代理以控制对这个对象的访问。我们可以使用代理模式在目标对象实现的基础上,以增加额外的功能操作或者逻辑,即可扩展目标对象的功能。

在平时其实我们经常会接触到代理模式影子,比如@Transcational注解实现的事务,以及一些服务方法调用前的安全、权限检查等。

接下来,我们一起来了解一下代理模式,并给出方法耗时时间打印的例子。

代理模式介绍

意图

代理模式的意图是为其它对象提供一种代理以控制对这个对象的访问。

结构

代理模式其基本的结构如下:

Subject

定义和RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。

RealSubject

定义Proxy所代表的实体。

Proxy

保存一个引用使得代理可以访问实体。

代理模式示例

接下来,我们使用代理模式完成方法拦截,计算方法执行耗时并打印执行时间为例子进行讲解。

1、静态代理 示例

假设我们有一个书本服务,包含查询方法。

Subject(BookService)

Book实体类,包含id、name、press、authors和price信息。

RealSubject(BookServiceImpl )

真实的服务接口,实现真实的业务逻辑操作。

Proxy(StaticBookServiceProxy

BookService的静态代理,实现BookService接口,并持有真实服务的引用。示例中,先记录执行前的时间,然后调用真实接口,最后记录耗时。

客户端Client

通过如下3个步骤调用:

  • 步骤1:  创建具体的服务对象
  • 步骤2: 创建代理服务对象
  • 步骤3: 调用代理对象方法

运行一下

这样,一个简单的静态代理模式代码就完成。

从上述示例可以看出,我们通过静态代理,在不改变真实服务的情况下,完成了方法执行耗时时间的记录

那么问题来了:

静态代理需要与目标一样的接口,所以一旦接口增加方法,目标对象与代理对象都需要维护。另外,还有很多其他的服务,比如UserService、OrgService ... ... ,  静态代理就会遇到各种瓶颈。

这个时候就需要用到动态代理了。动态代理我们一般采用基于JDK的动态代理以及基于Cglib的动态代理。动态代理是在程序运行时动态生成代理类,不需要手动编写代理类。

接下来就已这两种方式来完成示例。

2、基于JDK动态代理示例

要完成基于JDK的代理实现,需要实现InvocationHandler接口,如

客户端调整

执行输出

这样,一个基于JDK动态代理的实现就完成了。如果此时在客户端再调用一次,

bookServiceProxy.insert(book);, 也可以打印相关的耗时,有兴趣的读者可以尝试一下。

但是,我们知道: JDK动态代理的实现是需要基于接口的,不能对非接口的实体类进行代理。

那是否有方法实现非接口的代理呢,如对如下代码实现代理?

答案当然是肯定的,cglib可以帮助我们实现。接下来,就来看下基于cglib的代理实现。

3、基于Cglib实现动态代理示例

CGLIB(Code Generation Library)是一个基于ASM的高性能字节码生成库,可以在运行时动态生成Java类、方法、字段等,并且还能够修改已有的Java类的字节码。它广泛的被许多AOP的框架使用。

CGLIB实现动态代理的原理

通过继承目标类或实现目标类的接口,然后在代理类中重写目标类中的方法来实现对目标类的代理。

使用的时候需要引入相关的包,比如:

编写一个非接口的GreetingService类

编写一个方法拦截器实现MethodInterceptor

接下来,我们使用cglib来完成动态代理的调用,主要使用Enhancer来完成,如下:

执行一下,我们看到非接口GreetingService的方法执行耗时就打印出来了。

从上述示例我们可以看到,基于cglib能够对非接口的场景完成代理,其实现的方式是生成子类的方式完成的。所以一旦被代理的对象是final,那么就不能生成子类,也就受到限制了。

至于cglib代理详细的机制这里就不再展开,有兴趣的读者可以通过设置如下属性:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code-generate");

将cglib生成的class输出到指定目录,通过javap命令或者反编译工具进行查看,如下是我执行后class文件的输出和部分内容,大致可以看到其生成子类的方式了。

有兴趣的读者可以自己实操一下。

其它阅读点

JDK动态代理 vs. Cglib动态代理

JDK动态代理

基于接口的代理,它只能代理实现了接口的目标对象,使用Java的反射机制在运行时动态生成代理类。对于没有实现接口的目标对象,就不能使用JDK动态代理。

CGLIB代理

它可以代理没有实现接口的类。CGLIB通过生成目标对象的子类来实现代理,对目标对象的方法进行拦截和增强。

Spring框架使用的动态代理

Spring框架既支持JDK动态代理,也支持Cglib动态代理:

  • 当目标对象实现了接口时,就使用JDK动态代理;
  • 当目标对象没有实现接口时,就使用CGLIB代理。

代理模式 vs. 装饰模式

这两个模式,都属于结构型设计模式,它们的目的都是在不改变原有类的基础上,为原有类添加新的功能或行为,所以比较容易混淆。

稍微列举一下区别:

职责不同:

代理模式的主要作用是控制对对象的访问,而装饰模式的主要作用是动态地给对象添加行为。代理模式主要关注对于对象访问的控制,而装饰模式更侧重于对于对象功能的增强。 功能不同:

代理模式主要用于为一个对象提供访问控制,而装饰模式主要用于为一个对象添加功能。代理模式可以实现懒加载、缓存、远程代理等功能,而装饰模式可以动态地添加、删除、组合对象的功能。