静态代理和动态代理

清疚 2022-05-20 09:14 372阅读 0赞

Java 静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为代理三要素

共同接口

  1. public interface Action {
  2. public void doSomething();
  3. }

真实对象

  1. public class RealObject implements Action{
  2. public void doSomething() {
  3. System.out.println("do something");
  4. }
  5. }

代理对象

  1. public class Proxy implements Action {
  2. private Action realObject;
  3. public Proxy(Action realObject) {
  4. this.realObject = realObject;
  5. }
  6. public void doSomething() {
  7. System.out.println("proxy do");
  8. realObject.doSomething();
  9. }
  10. }

运行代码

  1. Proxy proxy = new Proxy(new RealObject());
  2. proxy.doSomething();

这里写图片描述

这种代理模式也最为简单,就是通过proxy持有realObject的引用,并进行一层封装。

静态代理的优点和缺点

优点: 扩展原功能,不侵入原代码。

缺点:

假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代码可能是这样的:

  1. realObject.doSomething();
  2. realObject1.doAnotherThing();
  3. realObject2.doTwoAnother();

为了解决这个问题,我们有方案一:

为这些方法创建不同的代理类,代理后的代码是这样的:

  1. proxy.doSomething();
  2. proxy1.doAnotherThing();
  3. proxy2.doTwoAnother();

当然,也有方案二:

通过创建一个proxy,持有不同的realObject,实现Action1、Action2、Action3接口,来让代码变成这样:

  1. proxy.doSomething();
  2. proxy.doAnotherThing();
  3. proxy.doTwoAnother();

于是你的代理模型会变成这样:
这里写图片描述

毫无疑问,仅仅为了扩展同样的功能,在方案一种,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。

而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。

Java 动态代理

搞清楚静态代理的缺点十分重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。

简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理的基本用法

动态代理可通过jdk动态代理和cglib动态代理来实现。

  • jdk动态代理:由java内部的反射机制来实现,基于接口
  • cglib动态代理:通过asm来实现,基于类

使用动态代理,需要将要扩展的功能写在一个InvocationHandler实现类里:

  1. public class DynamicProxyHandler implements InvocationHandler {
  2. private Object realObject;
  3. public DynamicProxyHandler(Object realObject) {
  4. this.realObject = realObject;
  5. }
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. //代理扩展逻辑
  8. System.out.println("proxy do");
  9. return method.invoke(realObject, args);
  10. }
  11. }

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

到这里,需要先看一下这个handler的用法:

  1. public static void main(String[] args) {
  2. RealObject realObject = new RealObject();
  3. Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
  4. proxy.doSomething();
  5. }

Proxy.newProxyInstance 传入的是一个ClassLoader, 一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。

仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的,有没有一点动态的味道。
这里写图片描述
让我们再回顾一下代理三要素:

  • 真实对象:RealObject
  • 代理接口:Action
  • 代理实例:Proxy

上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。

综上,动态生成+代理模式,也就是动态代理。

动态代理的优点和美中不足

  • 优点:

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。

  • 美中不足:

诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

转载自《静态代理和动态代理的理解》

发表评论

表情:
评论列表 (有 0 条评论,372人围观)

还没有评论,来说两句吧...

相关阅读

    相关 静态代理动态代理

    Java 静态代理 静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这

    相关 静态代理动态代理

    代理模式:   Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题   代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控