Java常用设计模式—代理模式

x33g5p2x  于2022-02-07 转载在 Java  
字(5.5k)|赞(0)|评价(0)|浏览(433)

代理模式

代理模式分为:静态代理、动态代理

  • 代理模式最直观的解释就是,通过代理,将被代理对象 “增强”!(即,扩展被代理对象的功能)
  • 代理模式分为静态代理,和动态代理:动态代理的代理类是动态生成的 , 静态代理的代理类是我们提前写好的逻辑。

1、静态代理

  • 抽象角色 :一般使用接口或者抽象类来实现。
  • 真实角色 :被代理的角色。
  • 代理角色: 代理真实角色 , 代理真实角色后 ,一般会做一些附属的操作。
  • 调用方:使用代理角色来进行一些操作。

我们以火车站售票的案例,涉及的对象有:购票人、火车站售票点、代售点(火车站售票点即为被代理对象,代售点即为代理对象)

购票人通过代售点买到火车站售票点的票,代售点为此收点费用!

代码实现:

  • SellTickets.java 即抽象对象
public interface SellTickets {
    void sell() ;
}
  • TrainStation 火车站售票点,真实对象
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站售票");
    }
}
  • ProxyPoint.java 代售点,代理对象
public class ProxyPoint implements SellTickets {

    private TrainStation trainStation = new TrainStation() ;

    @Override
    public void sell() {
        System.out.println("代理点先收点手续费!");
        trainStation.sell();
    }
}
  • Clinet.java 客户端,购票人
public class Client {
    public static void main(String[] args) {
        // 创建代售点对象
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

静态代理的缺点:

  • 需要手动创建代理类,如果需要代理的对象多了,那么代理类也越来越多。

为了解决,这个问题,就有了动态代理 !

2、动态代理

动态代理有两种实现方法:1、JDK实现动态代理 2、Cglib实现动态代理

前提:动态代理不存在代理类,而是在内存中动态的生成代理类!

2.1、JDK动态代理

Java 中提供了一个动态代理类 Proxy,Proxy 并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance 方法)来获取代理对象。

注:JDK 动态代要求必须定义接口,因为它只能对接口进行代理。

①、卖火车票的接口类:同静态代理。

public interface SellTickets {
    void sell() ;
}

②、火车站类:火车票具有卖票功能,实现 SellTickets 接口,同静态代理。

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

③、获取代理对象的工厂类:ProxyFactory.java 通过工厂类获取代理对象,

/***
 *  获取代理对象的工厂类
 *
 *  代理类也实现了对应的接口(此处接口指的是被代理对象实现的接口)
 */
public class ProxyFactory {

      //声明目标对象(被代理对象)
      private TrainStation station = new TrainStation() ;

      public SellTickets getProxyObject(){
        //返回代理对象
          /*
           * ClassLoader loader,   // 类加载器,用于加载代理类,可以通过目标对象获取类加载器!
           * Class<?>[] interfaces,  //代理类实现的接口的字节码对象
           * InvocationHandler h   //代理对象的调用处理函数
           */
          SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
                  station.getClass().getClassLoader(),
                  station.getClass().getInterfaces(),
                  new InvocationHandler() {

                      /*
                            Object proxy,   代理对象,根ProxyObject是同一个对象,在invoke方法中基本不用
                            Method method,  对接口中的方法进行封装的method对象,本例子中指的就是sell
                            Object[] args   传递的实际参数

                            返回值:方法的返回值
                       */
                      @Override
                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                          System.out.println("代理点收取一定的费用(JDK动态代理)");
                          Object obj = method.invoke(station, args);// 通过反射执行目标对象中的方法(火车站中的sell方法)!
                          return obj;
                      }
                  }
          );
          return proxyObject ;
      }
}

④、测试类client.java

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();  //代理对象同样实现了SellTickets,此处用到多态,编译执行的是sell方法,本质执行的是invoke方法
    }
}

测试结果 :

代理点收取一定的费用(JDK动态代理)
火车站售票

JDK动态代理底层的原理

思考问题:ProxyFactory 是代理类吗?

答:ProxyFactory 不是代理模式中所说的代理类,ProxyFactory 只是提供了一个生成代理对象的方法,而代理类是程序在运行过程中动

态的在内存中生成的。

  • 关于代理类,我们可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:(去除其他代码)

1、准备工作:通过生成的代理对象可以获取到代理类的名字

System.out.println(proxyObject.getClass());  //  class com.sun.proxy.$Proxy0

2、启动 Arthas【阿尔萨斯】,查看该代理类的结构 (注:已经删除非必要代码!)

// 程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {

  private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
      m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
      return;
    }
  
    public final void sell() {
      this.h.invoke(this, m3, null);
    }
}

我们看到:**1、**在静态代码块中==加载的是接口中的sell方法==,然后将其赋值给m3,**2、**代理类只存在一个有参构造,而且把参数通过

super(invocationHandler) ; 传递给父类Proxy中的成员变量 h ,这个invocationHandler就是我们的写的匿名内部类!

接下来就是:当我们代理对象调用sell 方法的时候,编译时执行的接口中的方法,运行时则是代理类中的sell方法,而代理类中的sell方法

public final void sell() {
      this.h.invoke(this, m3, null);
    }

会去调用我们匿名内部类invocationHandler中的invoke方法!

得出结论:代理对象的所有方法,都会被映射到invoke方法中执行 !
执行流程总结如下:

① 、在测试类中通过代理对象调用 sell() 方法

② 、根据多态的特性,执行的是代理类 $Proxy0 中的 sell() 方法

③ 、代理类 $Proxy0 中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法

④ 、invoke 方法通过反射执行了真实对象所属类 TrainStation 中的 sell() 方法

2.2、CGLIB动态代理

上面的案例中,如果没有定义 SellTickets 接口,只定义了 TrainStation 火车站类。则 JDK 代理无法使用,因为 JDK 动态代理要求必须定

义接口,它只能对接口进行代理。

CGLIB 是一个功能强大,高性能的代码生成包,它可以为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充

CGLIB 是第三方提供的包,所以需要引入依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

代码如下:CGLIB 动态代理可以不代理接口,直接代理类。

①、火车站类:火车票具有卖票功能

public class TranStation {

    public void sell(){
        System.out.println("火车站卖票!");
    }
}

②、代理工厂类:用来获取代理对象

/**
 *  代理对象工厂 : 用于获取我们的代理对象
 *
 *  cglib的代理类,属于目标类的子类
 */
public class ProxyFactory implements MethodInterceptor {

    private TranStation tranStation = new TranStation() ;

    public TranStation getProxyObject(){
        //创建Enhancer对象 , 类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer() ;
        //设置父类的字节码对象
        enhancer.setSuperclass(TranStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TranStation proxyObject = (TranStation) enhancer.create() ;

        return proxyObject ;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一定的费用(CGLIB实现动态代理)");
        //调用目标类的方法
        Object obj = method.invoke(tranStation, objects);
        return obj;
    }
}

2.3、三种代理的对比

两种动态代理的对比:JDK 代理CGLib 代理

  • JDK 动态代理本质上是实现了被代理对象的接口,而 CGLib 本质上是继承了被代理对象,覆盖其中的方法

  • JDK 动态代理只能对实现了接口的类生成代理,CGLib 则没有这个限制。但是 CGLib 因为使用继承实现,所以 CGLib 所以无法对 final 类、private 方法和 static方法进行代理。

  • JDK 动态代理是 JDK 里自带的,CGLib 动态代理需要引入第三方的 jar 包。

  • 在性能上,JDK1.7 之前,由于使用了 FastClass 机制,CGLib 在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从 JDK 1.7 开始,JDK 动态代理已经明显比 CGLib 更快了。所以,JDK1.8 以后,如果有接口就使用 JDK 动态代理,没有接口就使用 CGLib 代理。
    动态代理和静态代理的对比:

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

  • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题。

2.4、动态代理的优缺点

优点:

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

缺点:

  • 增加了系统的复杂度;
    面试题

JDK 动态代理为什么只能对实现了接口的类生成代理?

答:根本原因是通过 JDK 动态代理生成的类已经继承了 Proxy 类,所以无法再使用继承的方式去对类实现代理(类只支持单继承)。

相关文章