设计模式-代理Proxy

1.静态代理:

  • 目标:
    • 在坦克对象已有的move业务中添加记录的功能,
    • 记录坦克什么时候移动,什么时候结束
  • 分析:
    • 首先想到的可能会是继承,
      • 但是继承耦合性太高了,
      • 不同功能的实现需要多种子类单独实现,不利于扩展
    • 有什么办法可以优化呢
      • 代理的方式可以直接写在代理中,需要的时候新建代理,并把对象传过去。

image.png

实现过程

  1. 代理和被代理对象都实现了Movable接口
  2. 在代理里边有一个被代理对象
  3. 代理有自己的move方法,加自己的逻辑进去,并调用被代理对象的move方法

image.png

如何实现多个代理的组合

image.png

  • 直接嵌套,因为都是实现了Movable接口

image.png

动态代理:

  • 静态只能代理Movable行为,我希望他可以代理各种各样的行为,怎么做?
    • 代理类不是手写的,动态生成的。

实现过程

  • 上代码,使用的时候看上去非常简单,但是解释起来非常麻烦

image.png

  1. 仍然定义Movable接口,被代理对象实现接口,实现move接口
  2. 使用Proxy类,他的包是java的reflect(反射:所谓的反射就是用二进制字节码分析类的属性和方法)
    • 三个参数:
      • ClassLoader:你要用哪个ClassLoader把将来的对象load到内存,跟代理对象同一个就可以了
      • 接口数组:代理对象应该实现哪些接口?
      • InvocationHandler:调用时候的处理器,被代理对象那个方法被调用的时候我们怎么处理?

image.png

  • move最终调用invoke

image.png

  • 结果

image.png

原理

  • move方法经过一系列调用以后,调用了invoke方法
  • 他是怎么调用的呢?
    • 加一行代码试试

image.png

  • 添加了一行代码,什么意思?
    • jdk代理产生器里面有一个属性,叫保存生成文件,设置为true。
    • 执行一下,生成了文件

image.png

  • 这个就是中间动态生成的类,本来是看不到的,设置了刚才的属性,就会反编译生成出来

image.png

  • 顶部的提示,说明这个文件是反编译出来的,是class文件类型
  • 来看一下完整的代码

image.png

image.png
再一步一步分析这个类:

  1. 先看构造,LogHandler传进来,父类保存了

image.png
2. 他生成了move方法,实现了Movable接口,

  • 为什么实现Movable接口,
  • 因为调用动态生成的时候,第二个参数指定了要实现。
  1. 调用了父类h的invoke方法

image.png

  • h是什么?
    • 就是父类保存的传进来LogHandler,也就是第三个参数

image.png

image.png

  • H就是传进去的第三个参数,最终调用的是他的invoke

image.png

  • invoke的参数代表什么

    • 第一个参数:生成的代理对象,就是Movable m ;
    • 第二个参数是调用的哪个方法
    • 第三个是方法的参数
  • 中间的那一行代码相当于调用了tank.move();(中间涉及到Asm——-下面会解释)

image.png

总结一下调用过程

  • jdk动态代理全部过程:
    1. Client,相当于main所在的类,调用newProxyInstance()
    2. Proxy通过一系列操作生成了Proxy$0(下面细讲),
    3. 然后new了一个他的对象,并且将InvocationHandle作为参数传进去。
    4. 既然生成了,Client就可以调用他,调用move方法,在Proxy$0的move方法里面调用了invoke方法
    5. 实际调用的是invocationHandler的invoke方法
    6. 在move的前半截书写逻辑,后半截也可以书写逻辑,
    7. 然后调用的坦克的move方法

    image.png

Asm

  • Proxy$0.class是怎么生成的呢?
  • 扒一下源码

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

  • 最终add方法是由jdk的asm类库实现的:
  • 所以想把动态代理弄明白的话,得把asm弄明白。
  • Asm干嘛的?
    • 他直接操纵二进制数据,
  • class文件不是可以直接load到内存吗?
    • 我们改class文件的话先要改源码,源码编译后改变
    • 但是asm可以直接改二进制码,直接修改class文件。
  • 用了asm以后,java才可以被称为动态语言。
    • 所谓动态语言,就是运行时可以直接改变类里面的属性和方法
    • 反射只能找出来,但是改不了。
  • asm怎么操作二进制?
    • 首先要明白二进制里面每一段代表都是什么?

image.png

  • 这些东西每一个都是十六进制数,展开都是0101之类的,所以就是直接把0101的内容改掉。
  • 改的时候需要知道哪个是方法名,哪个是返回值,哪个代表的类名,哪个是接口,直接把二进制码改了。
  • 这个就是asm干的时候。
  • 要想干这件事,首先得明白class类文件的结构是什么?(后续补充————————)
    • 多数人都知道咖啡baby,任何class类开头都是这个,后面的内容有十种类型。

那现在好玩了,我不写java,直接通过编译器生成class文件,你就可以在jvm上运行,这就是scala、kotlin。所以JVM现在是一个大生态。

image.png

缺点

动态带的缺陷在于你那个类必须实现某个接口

扩展

还有比较牛逼的方法:

1.Instrument
  • 本意是给琴调琴弦,java自带的
  • 他是一个钩子函数,是一个拦截器,
    • class加载到内存的过程中,他会拦截calss,
    • 在拦截的时候,可以对class进行一些自己的定制。
    • 相当于你可以直接修改二进制码
  • 这个功能非常强大,比asm还要强大,他可以完全的控制二进制码
    • Asm我们是通过jdk来用的,他api并不是说所有功能都能实现,新版本出来了api没跟上就实现不了,
    • 但是这种方法是完全可以实现的
    • 只是做的时候非常繁琐,你必须了解class文件里面每一个0,每一个1什么意思。

还有一个比较简单的

2.cglib
  • 代码生成的lib
  • 如果你用cglib生成代理的话,要比jdk的反射生成要简单很多
    • tank不用实现任何借口,

Mian方法里面,enhance本意是增强
image.png

  • 使用cglib生成代理需要添加依赖:

image.png

  • cglib生成代理不需要接口?
    • 那我怎么知道我在代理类里面实现了哪个方法呢?
    • 从这里可以看出来,我们生成的动态代理类是Tank的一个子类,

image.png

  • 在intercept方法被调用的时候,第一个参数跟jdk生成的参数是一样的。就是生成动态代理类的对象。
  • 打印他的父类可以发现是Tank。

image.png

  • 如果我们的Tank类是final 的,还能用cglib生成代理吗?
    • 生成不了,所以cglib也有自己的缺陷,
    • asm就可以,因为他直接操纵的二进制码,
      • asm完全可以做到,
      • 他将整个class文件读出来,里面是一堆方法一堆属性
      • 然后它把这些方法属性读出来,可以重新写一个新的class文件,可以直接将final去掉
    • cglib的底层也是用的asm
    • Asm一般了解就可以了

对比

  • 动态代理和静态代理的对比:
    • 动态代理可以非常灵活地指定有多少种代理,哪种排第一位,哪种排第二位
      • 比如现在有一个业务的逻辑都写好了,生产、库存、订单…
      • 现在我有另外一个需求,我需要在所有操作上添加一个权限检查
        • 现在怎么做?
          • 一种是找到所有代码,里面都加上写一遍,
          • 第二种是生成动态代理,相当于在一个切面植入

补充:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享