博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
厚积薄发打卡Day36 :[itcast] GoF23通俗易懂的设计模式之 <代理模式>
阅读量:365 次
发布时间:2019-03-04

本文共 9809 字,大约阅读时间需要 32 分钟。

前言:

视频教程:

什么是设计模式?

  • 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
  • 1995年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,人称 【GoF设计模式】
设计模式分类 具体模式
创建型模式:
它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
⌛单例模式、⌛工厂模式、⌛抽象工厂模式、⌛建造者模式、⌛原型模式
结构型模式:
结构型模式描述如何将类或对象按某种布局组成更大的结构。
⌛适配器模式、⌛桥接模式、⌛装饰模式、⌛代理模式、组合模式、外观模式、享元模式、
行为型模式:
这些设计模式特别关注对象之间的通信。
模板方法模、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

代理模式

概述:

代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理动态代理

  • 静态代理:代理类在编译期就生成。
  • 动态代理代理类则是在Java运行时动态生成(两种):
    • JDK代理
    • CGLib代理

代理模式结构:

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject) 类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject) 类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
    • 真正执行的对象
  • 代理(Proxy) 类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

静态代理

代理类在编译期就生成

通过 火车站卖票 的例子说明静态代理:

  • 【顾客】 通过 【代售点】 去买 【火车站】 卖出的火车票

类图如下:

在这里插入图片描述

  • 卖票方法(接口):

    //卖票接口:抽象主题(Subject)类public interface SellTickets {
    void sell();}
  • 火车站:

    public class TrainStation implements SellTickets {
    @Override public void sell() {
    System.out.println("火车站卖票"); }}
  • 代售点:

    public class ProxyPoint implements SellTickets {
    //直接在此实例化火车站对象 private TrainStation station = new TrainStation(); @Override public void sell() {
    System.out.println("代理点收取一些服务费用..."); station.sell(); }}
  • 顾客:

    public class Client {
    public static void main(String[] args) {
    ProxyPoint proxyPoint = new ProxyPoint(); proxyPoint.sell(); }}

    看起来是去代售点买票,实则仍是买火车站的票

    代理点收取一些服务费用...火车站卖票

    此时静态代理的作用:

    • 避免了直接访问火车站(顾客离火车站太远只能通过代售点买票),
    • 同时对sell()方法进行了一定增强(代售点收取了一定的手续费)

JDK动态代理

静态代理是在代理类的一开始就声明了RealSubject对象:

  • 如上述例子的代售点类中,一开始便直接实例化了 火车站对象,通过火车站对象直接调用卖票方法

动态代理则不用事先实例化对象,在运行途中由内存创建。此处先讲解JDK的动态代理:

  • 在Java中提供了一个代理类:Proxy (JDK1.3提供)

    在这里插入图片描述

    Proxy 作为 动态代理类,提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象: 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

    @CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,                                      Class
    [] interfaces, InvocationHandler h)
    • newProxyInstance()方法参数说明: -      ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可 -      Class
      [] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口 - InvocationHandler h : 代理对象的调用处理程序
    • InvocationHandler中invoke方法参数说明:

      • proxy : 代理对象
      • method : 对应于在代理对象上调用的接口方法的 Method 实例
      • args : 代理对象调用接口方法时传递的实际参数
      //同时我们发现InvocationHandler是一个函数式接口,因此我们可以用lambda表达式来写:public interface InvocationHandler {
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}

上代码:

  • 由于【卖票方法(接口)】与【火车站类】与静态代理一致,不再赘述

  • 代理工厂类:

    //代理工厂,用来创建代理对象public class ProxyFactory {
    private TrainStation station = new TrainStation(); public SellTickets getProxyObject(){
    //使用代理类获得代理对象: //1.获得其类加载器 //2.获得真实对象所实现的接口 //3.调用InvocationHandler处理真实对象 SellTickets sellTickets = (SellTickets)Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), (proxy,method,args)->{
    System.out.println("(jdk动态代理)代售点收取了一定的手续费用..."); //执行真实对象: return method.invoke(station, args); }); return sellTickets; }}

    特此注释下lambda表达式:

    //lambda表达式注释:Proxy.newProxyInstance(station.getClass().getClassLoader(),                       station.getClass().getInterfaces(),                       new InvocationHandler() {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); //执行真实对象 Object result = method.invoke(station, args); return result; } });
  • 测试类:

    public class Client {
    public static void main(String[] args) {
    //获取代理对象 //1.创建代理工厂对象 ProxyFactory factory = new ProxyFactory(); //2.使用factory对象的方法获取代理对象 SellTickets proxyObject = factory.getProxyObject(); //3.调用卖票的方法 proxyObject.sell(); }}
  • 结果:

    (jdk动态代理)代售点收取了一定的手续费用...火车站卖票

探究代理方法

在代理工厂中,我们并没有直接通过实例去调用sell()方法,那是如何动态生成代理模式中的代理类呢?

通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

Arthas

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?

因动态代理是在 内存中动态生成代理类,因此需要将程序保持在运行中才能通过工具发现其代理类:

  • 首先在client中加上一个死循环:

    此时程序就会保持在运行状态,同时输出代理对象的类名,方便用工具查看:

    在这里插入图片描述

  • 在cmd/powershell使用Arthas工具 在这里插入图片描述

    得到真正的动态代理:

    • 代理类($Proxy0)实现了SellTickets。

      这也就印证了我们之前说的真实类和代理类实现同样的接口。

    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

    筛出其重点代码可得到其执行过程:

    //程序运行过程中动态生成的代理类public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3; public $Proxy0(InvocationHandler invocationHandler) {
    super(invocationHandler); } static {
    m3 = Class.forName("com.kuangstudy.design_pattern.proxy.jdk_proxy.SellTickets").getMethod("sell", new Class[0]); } public final void sell() {
    this.h.invoke(this, m3, null); }}
    • 执行流程如下:

      1. 在测试类中通过代理对象调用sell()方法2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

CGLib动态代理

同样是通过去代理点买火车票的例子:

可以看到在newProxyInstance()方法中需要由Class<?>[] interfaces参数,也就是说必须要定义接口,才能对接口进行代理。

假如没有接口的情况下,这时就应该使用CGLib来实现动态代理:

  • CGLIB是一个功能强大,高性能的代码生成包。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

  • 它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

  • CGLIB是第三方提供的包,所以需要引入jar包的坐标:

    cglib
    cglib
    2.2.2
  • 实现MethodInterceptor接口需要重写intercept()方法:

    intercept方法参数说明:      o : 代理对象      method : 真实对象中的方法的Method实例      args : 实际参数      methodProxy :代理对象中的方法的method实例

上代码:

  • 火车站:

    public class TrainStation  {
    public void sell() {
    System.out.println("火车站卖票"); }}
  • 代理工厂:

    public class ProxyFactory implements MethodInterceptor {
    private TrainStation station = new TrainStation(); public TrainStation getProxyObject(){
    //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer =new Enhancer(); //设置父类的字节码对象 enhancer.setSuperclass(station.getClass()); //设置回调函数 enhancer.setCallback(this); //创建代理对象 TrainStation obj = (TrainStation) enhancer.create(); return obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); TrainStation result = (TrainStation) methodProxy.invokeSuper(o, objects); return result; }}
  • 顾客:

    public class Client {
    public static void main(String[] args) {
    //创建代理工厂对象: ProxyFactory proxyFactory = new ProxyFactory(); //获取代理对象: TrainStation proxyObject = proxyFactory.getProxyObject(); //调用卖票方法: proxyObject.sell(); }}
  • 运行结果:

    代理点收取一些服务费用(CGLIB动态代理方式)火车站卖票

探究CGLib代理方法

arthas再学习:

步骤方法与探究jdk动态代理方法一致:

在这里插入图片描述

好家伙,这也太长了吧这个类,挑了点重点代码看一看:

public class TrainStation$$EnhancerByCGLIB$$d50d37f9    extends TrainStation    implements Factory {
public TrainStation$$EnhancerByCGLIB$$d50d37f9() {
TrainStation$$EnhancerByCGLIB$$d50d37f9 trainStation$$EnhancerByCGLIB$$d50d37f9 = this; TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$BIND_CALLBACKS(trainStation$$EnhancerByCGLIB$$d50d37f9); } static {
TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$STATICHOOK1(); } //继承了TrainStation ,并调用super.sell()方法 public final void sell() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0; if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$d50d37f9.CGLIB$BIND_CALLBACKS(this); methodInterceptor = this.CGLIB$CALLBACK_0; } if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy); return; } super.sell(); }}

小结

对比:

  • JDK代理和CGLIB代理

    • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。
    • 唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类,而被声明了final类的话是不会有子类的(需要继承,而final类不能被继承)
    • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,
    • 但是到JDK1.8的时候,JDK代理效率高于CGLib代理。

    所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    • 动态代理与静态代理相比较,最大的好处是 接口/类 中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    • 如在卖票方法中增加卖快餐的方法,静态代理就需要通过实例再去调用这个方法。
    • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
    • 动态代理不会出现该问题

优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点:

  • 增加了系统的复杂度;

代理和装饰者的区别

静态代理和装饰者模式的区别:

  • 相同点:
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点:
    • 目的不同
      • 装饰者是为了增强目标对象
      • 静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
      • 装饰者是由外界传递进来,可以通过构造方法传递
      • 静态代理是在代理类内部创建,以此来隐藏目标对象

应用实例:

  1. jdk动态代理在mybatis中的应用,主要通过 MapperProxyFactory实现

    如果使用过Mybatis,我们就会发现Mybatis的使用非常简单,首先定义一个dao接口,然后编写一个与dao接口的对应的配置文件,java对象与数据库字段的映射关系和dao接口对应的sql语句都是以配置的形式写在配置文件中,非常的简单清晰

    但是笔者在使用的过程中就曾经有过这样的疑问,dao接口是怎么和mapper文件映射起来的呢?只有一个dao接口又是怎么以对象的形式来实现数据库的读写操作呢?相信有疑问的肯定不止我一个人,当然,在看了上面两节之后,应该很容易猜到可以通过代理模式来动态的创建dao接口的代理对象,并通过这个代理对象来实现数据库的操作。

  2. Spring中的AOP实现

转载地址:http://yhzg.baihongyu.com/

你可能感兴趣的文章