一 概述

1.1 简述

1)由于某些原因需要给对象提供一个代理以控制该对象的访问。这时,访问对象不适合不能直接引援目标对象,代理对象作为访问对象和目标对象之间的中介。也就是说:代理对象充当中介成分。
2)Java中的代理按照代理类生成时机的不同,又分为静态代理动态代理。静态代理在编译期的时候就生成。而动态代理则是在Java运行之后再动态生成的。动态代理又分JDK代理CGLib代理

1.1 三个角色

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

二 静态代理

1)静态代理在编译期的时候就生成
2)如:火车票的售卖,在各个地方有很多代售点,火车站就是目标对象,代售点就是代理对象。

  • 卖票接口

    1. public interface SellTicket {
    2. void sellTicket();
    3. }
  • 火车站目标对象

    1. public class TrainStation implements SellTicket {
    2. @Override
    3. public void sellTicket() {
    4. System.out.println("卖火车票");
    5. }
    6. }
  • 代售点代理对象

    1. public class ProxyStation implements SellTicket {
    2. private TrainStation trainStation;
    3. public ProxyStation(SellTicket sell) {
    4. this.trainStation = sell;
    5. }
    6. @Override
    7. public void sellTicket() {
    8. System.out.println("代售点收取服务费");
    9. trainStation.sellTicket();
    10. }
    11. }
  • 测试

    1. public class StationTest {
    2. public static void main(String[] args) {
    3. // 代理对象
    4. ProxyStation proxyStation = new ProxyStation();
    5. // 代理对象调用代理方法
    6. proxyStation.sellTicket();
    7. }
    8. }

    三 JDK动态代理

    Java提供了两个类给我们使用代理模式:Proxy,InvocationHandler

Java提供了一个动态代理类Proxy,这个类并不是上面那个代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance()方法)来获取代理对象。

  1. public class ProxyFactory {
  2. // 声明目标对象
  3. private TrainStation trainStation = new TrainStation();
  4. // 获取代理对象
  5. public SellTicket getProxy() {
  6. /**
  7. * newProxyInstance()方法需要传入的三个参数
  8. * 第一个:ClassLoader loader 类加载器,用于加载代理类,可以通过目标对象获取
  9. * 第二个:Class<?>[] interfaces 代理类实现的接口的字节码对象
  10. * 第三个:InvocationHandler h 代理对象的调用处理程序
  11. */
  12. SellTicket sellTicket = (SellTicket) Proxy.newProxyInstance(
  13. trainStation.getClass().getClassLoader(),
  14. trainStation.getClass().getInterfaces(),
  15. new InvocationHandler() {
  16. /**
  17. * @param proxy 代理对象,和返回的代理对象是同一个,在invoke方法中基本不使用
  18. * @param method 对接口中的方法进行封装的method对象
  19. * @param args 调用方法的实际参数
  20. */
  21. @Override
  22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  23. System.out.println("代售点收取服务费(JDK动态代理)");
  24. // 执行目标对象的方法
  25. Object invoke = method.invoke(trainStation, args);
  26. return invoke;
  27. }
  28. }
  29. );
  30. return sellTicket;
  31. }
  32. }

四 CGLib动态代理

1)如果没有实现SellTicket接口,只定义了TrainStation目标类。很显然JDK动态代理就无法使用了,因为JDK动态代理必须定义接口,对接口进行代理。
2)CGLib是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
3)CGLib是第三方提供的包,需要引入Jar包的坐标。

  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib</artifactId>
  4. <version>2.2.2</version>
  5. </dependency>
  • 工厂代理类,需要实现MethodInterceptor接口
    public class ProxyFactory implements MethodInterceptor {
      private TrainStation trainStation = new TrainStation();
      // 获取代理对象
      public TrainStation getProxy() {
          // 创建EnHancer对象,类似于JDK代理中的Proxy类
          Enhancer enhancer = new Enhancer();
          // 设置父类的字节码对象
          enhancer.setSuperclass(TrainStation.class);
          // 设置回调函数
          enhancer.setCallback(this);
          // 创建代理对象
          TrainStation proxyObj = (TrainStation) enhancer.create();
          return proxyObj;
      }
      @Override
      public Object intercept(Object o, Method method, Object[] objects, 
                              MethodProxy methodProxy) throws Throwable {
          System.out.println("代售点收取服务费");
          trainStation.sellTicket();
          return null;
      }
    }
    

    五 三种模式对比

    5.1 JDK和CGLib代理

    1)使用cglib实现动态代理,cglib底层采用ASM字节码作为生成框架, 使用字节码技术生成代理类,在JDK1.6之前比Java反射效率要高。唯一需要注意的是,cglib不能对声明为final的类或者方法进行代理,因为cglib原理是动态生成目标类的子类。
    2)在JDK1.6,JDK1.7,JDK1.8逐步的对JDK动态代理优化后,在调用次数较少的情况下,JDK的代理效率高于cglib的效率。只有进行大量调用的时候,JDK1.6和1.7比cglib效率低一点。但是到了JDK1.8之后,JDK代理效率远高于cglib代理。所以如果有接口使用JDK动态代理,没有接口的时候就使用cglib进行代理。

    5.2 动态代理和静态代理

    1)动态代理与静态代理相比较,最大的好处就是在接口中声明的所有方法都被转移到处理的一个集中方法进行处理(invocationHandler.invoke)。这样在接口方法数量较多的时候,我们可以进行灵活处理。而不需要像静态代理那样每一个方法都进行中转。
    2)如果接口增加一个方法,静态代理模式处理所有实现类需要实现这个方法之外,所有代理类也需要实现这个方法,增加了代码维护的复杂度。而动态代理不会出现这个问题。