zoukankan      html  css  js  c++  java
  • Java动态代理与静态代理以及它能为我们做什么

      相信我们在网上和平时学习和工作中或多或少都接触过Java的代理模式,经常听到什么静态代理、动态代理的一些名词。但我们是否真的很清楚这些呢?至少我在面试时,发现很多人并不很清楚。

      首先代理比较好理解,就是帮一个人,或者一类人做一些事情。迁移到面向对象的程序设计中,代理就是帮一个类去做一些事情,而这个代理的工具我们就称为代理类。

      通过代理的方式去做事有什么好处呢?这就好比工厂和分销商做的事情一样,工厂可以直卖一些自己的产品,分销商同样也可以卖工厂生产的产品,那么为什么还有分销商的存在呢?因为分销商可以提供一些额外的服务,或者在销售的过程中能够完成一些其他的事情,比如组合销售、根据本地情况做活动等,而这些可能是工厂不想关心或者也管不过来的。这样的功能和角色承包给代理商就会使得分工比较明晰,并且又能够提供一些额外或者定制的服务。

    静态代理

      Java中的代理方式可以分为静态代理和动态代理。静态代理的含义是代理类/对象在我们关心的程序运行前就已经确定或存在。静态代理比较好理解,我们在日常工作中也是经常用到,比如一个已经存在的接口,我们不期望去更改它,但是现在要在原逻辑上新加一些逻辑或功能,比如原接口方法调用完成后发送一个消息之类的。于是我们可以创建一个类,同样实现原接口,并且把之前存在的接口当做成员变量注入进来,调用其中的方法,并添加我们需要的功能。

      静态代理的类图如下所示,需要被代理的实现类和代理类都实现了抽象接口AbstractInterface,而InterfaceProxy和InterfaceImpl间是聚合关系。

      

       来看一段示例代码,ProductAuditCallbackService 是我们已有的一个接口,出于某些原因,这个接口不能继续对外使用,我们需要定义一个新的接口并且名称还要一样(主要是方便客户理解和对应原接口),但是我们需要添加一点“新逻辑”。因此我们可以同样实现 ProductAuditCallbackService,ProductAuditCallbackServiceProxy 就是我们的代理类,之后外部调用就可以实例化我们的代理类,调用同名方法就好了。

     1 public class ProductAuditCallbackServiceProxy implements ProductAuditCallbackService {
     2 
     3     @Resource
     4     private ProductAuditCallbackService productAuditCallbackService;
     5 
     6     @Override
     7     public Result<Void> auditProduct(ProductAuditRequest request, String auditStatus) {
     8         if (auditStatus == "DELETED") {
     9             return new Result<>();
    10         }
    11         return productAuditCallbackService.auditProduct(request, auditStatus);
    12     }
    13 
    14 
    15 ...
    16 }

    动态代理

      动态代理的作用和静态代理一样,主要的区别就在于需要在运行时生成代理类。在使用动态代理时,我们还需要定义一个在代理类和委托类之间的中介类,并且中介类需要实现 java.lang.reflect.InvocationHandler 接口。

     1 package java.lang.reflect;
     2 
     3 /**
     4  * {@code InvocationHandler} is the interface implemented by
     5  * the <i>invocation handler</i> of a proxy instance.
     6  *
     7  * <p>Each proxy instance has an associated invocation handler.
     8  * When a method is invoked on a proxy instance, the method
     9  * invocation is encoded and dispatched to the {@code invoke}
    10  * method of its invocation handler.
    11  *
    12  * @author      Peter Jones
    13  * @see         Proxy
    14  * @since       1.3
    15  */
    16 public interface InvocationHandler {
    17 
    18     public Object invoke(Object proxy, Method method, Object[] args)
    19         throws Throwable;
    20 }

      

      动态代理在框架类的代码中用到的频率并不低,而且能够使我们的代码看起来更高级一些,所以何乐而不为呢? 让我们来看一些实际的例子。

      MethodInvocationHandler是一个中介类,实现了InvocationHandler接口,MethodMonitor 这个类的功能就是要统计我们的委托类的对象business中的方法被调用的次数和耗时,由于其主要功能不是我们关注的主要内容,所以忽略其实现。

     1 public class MethodInvocationHandler implements InvocationHandler {
     2 
     3     //被代理对象
     4     private Object business;
     5 
     6     private final MethodMonitor methodMonitor;
     7 
     8     public MethodInvocationHandler(MethodMonitor methodMonitor) {
     9         this.methodMonitor = methodMonitor;
    10     }
    11 
    12     /**
    13      * 代理方法
    14      */
    15     @Override
    16     public Object invoke(Object proxy, Method method, Object[] args)
    17             throws Throwable {
    18 
    19         long startTime = System.currentTimeMillis();
    20 
    21         Object result = method.invoke(this.business, args);
    22 
    23         //方法调用统计
    24         this.methodMonitor.methodCount(this.business.getClass().getSimpleName() + POINT + method.getName(), startTime);
    25         return result;
    26     }
    27 
    28 }
    
    

      其余示例代码及外部调用示例如下,我们的Business类里面拥有三个方法。MethodSampleClient 则是一个封装起来的客户端。我们不想让外部客户端感知我们的实现以及和Business的关系,于是我们在MethodSampleClient中定义了一个成员变量proxy,当外部需要Business提供的一些功能时,我们通过proxy为其提供。Proxy.newProxyInstance() 则是我们实例化一个代理类的方式,哟,这还是个工厂模式,可以阅读一些这个方法的说明,需要传入的三个参数依次是:需要被代理的类的ClassLoader,被代理类需要被代理的接口的集合,中介处理类的实例。

      这里Business我写的是一个确定的类,其实真正在实际开发工作中,我们往往定义的抽象的接口或抽象类,知道运行时才会确定到底是哪个实现类的实例,这样可能更容易理解一些:运行时确定委托类的实现类,运行时生成代理类,并调用对应的委托类的方法。

     1 public class Business {
     2 
     3     public void createJob() {
     4         System.out.println("test createJob");
     5     }
     6 
     7 
     8     public void processJob() {
     9         System.out.println("test processJob");
    10     }
    11 
    12     public void closeJob() {
    13         System.out.println("test closeJob");
    14     }
    15 
    16 }
    17 
    18 
    19 
    20 public class MethodSampleClient {
    21 
    22     private Business business;
    23 
    24     @Getter
    25     private Object proxy;
    26 
    27     private InvocationHandler invocationHandler;
    28 
    29 
    30     public void init() {
    31         this.business = new Business();
    32         this.invocationHandler = new MethodInvocationHandler(new MethodMonitor());
    33         this.proxy = bind(this.business, invocationHandler);
    34     }
    35 
    36     /**
    37      * 绑定对象, 直接初始化并返回代理类供客户端使用
    38      */
    39     public Object bind(Object business, InvocationHandler invocationHandler) {
    40         return Proxy.newProxyInstance(
    41                 //被代理类的ClassLoader
    42                 business.getClass().getClassLoader(),
    43                 //要被代理的接口,本方法返回对象会自动声称实现了这些接口
    44                 business.getClass().getInterfaces(),
    45                 //代理处理器对象
    46                 invocationHandler);
    47     }
    48     
    49 }    
    50 
    51 
    52 /**
    53 *  A simple client test class
    54 */
    55 public class Test {
    56 
    57     public void main(String[] args) {
    58         MethodSampleClient methodSampleClient = new MethodSampleClient();
    59         methodSampleClient.init();
    60 
    61         methodSampleClient.getProxy().createJob();
    62         methodSampleClient.getProxy().processJob();
    63         methodSampleClient.getProxy().closeJob();
    64     }
    65 
    66 }

       

      为了说清楚这个过程,竟然还真的写了不少代码,看起来比较繁琐。总结一下,动态代理无非按照下面的步骤来编写代码:

    • 首先明确需要被代理的委托类。
    • 实现 InvocationHandler 接口,定义一个中介类。
    • 用 Proxy.newProxyInstance() 实例化代理类,并在客户端代码中直接使用。

      好了,大概差不多了,最重要的是能够在实际工作中有意识地去使用并体会其作用 —— 软件开发是经验驱动不是知识驱动。

      

  • 相关阅读:
    Maven的配置文件-settings.xml内容分解
    数据库管理工具-Navicat Premium 12
    转:android Support 兼容包详解
    转:聊聊mavenCenter和JCenter
    转:serialVersionUID作用
    Android 6.0 权限知识学习笔记
    X86和X86_64和X64有什么区别?
    Android 问题汇总(持续更新)
    Android-armebi-v7a、arm64-v8a、armebi的坑
    HttpUrlConnection 基础使用
  • 原文地址:https://www.cnblogs.com/XiaoHDeBlog/p/12996876.html
Copyright © 2011-2022 走看看