首頁 > Java > Java基礎 > 靜態代理和動態代理的差別是什麼?

靜態代理和動態代理的差別是什麼?

青灯夜游
發布: 2020-12-04 10:52:34
原創
4577 人瀏覽過

區別:靜態代理由程式設計師創建或工具產生代理類別的源碼,再編譯代理類別;程式運行前已經存在代理類別的字節碼文件,代理類別和委託類別的關係在運行前就確定了。動態代理類別的源碼是程式運作期間由JVM根據反射等機制動態的生成,所以不存在代理類別的字節碼檔案。

靜態代理和動態代理的差別是什麼?

相關推薦:《程式設計影片課程

一、代理商概念

為某個物件提供一個代理,以控制對這個物件的存取。代理類別和委託類別有共同的父類別或父接口,這樣在任何使用委託類別物件的地方都可以用代理物件替代。代理類負責請求的預處理、過濾、將請求分派給委託類處理、以及委託類執行完請求後的後續處理。 相關推薦:《Java影片教學

圖1:代理模式 

靜態代理和動態代理的差別是什麼? 

從圖中可以看出,代理介面(Subject)、代理類別(ProxySubject)、委託類別(RealSubject)形成一個「品」字結構。 
根據代理類別的產生時間不同可以將代理程式分為靜態代理程式和動態代理程式兩種。 

以下以類比需求說明靜態代理與動態代理:委託類別要處理一項耗時較長的任務,客戶類別需要列印執行任務消耗的時間。解決這個問題需要記錄任務執行前時間和任務執行後時間,兩個時間差就是任務執行消耗的時間。

二、靜態代理程式 

#由程式設計師建立或工具產生代理類別的源碼,再編譯代理類。所謂靜態也就是在程式運作前就已經存在代理類別的字節碼文件,代理類別和委託類別的關係在運作前就確定了。

清單1:代理介面 

/**  
 * 代理接口。处理给定名字的任务。 
 */  
public interface Subject {  
  /** 
   * 执行给定名字的任务。 
    * @param taskName 任务名 
   */  
   public void dealTask(String taskName);   
}
登入後複製

清單2:委託類,具體處理業務。  

/** 
 * 真正执行任务的类,实现了代理接口。 
 */  
public class RealSubject implements Subject {  
  
 /** 
  * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间 
  * @param taskName  
  */  
   @Override  
   public void dealTask(String taskName) {  
      System.out.println("正在执行任务:"+taskName);  
      try {  
         Thread.sleep(500);  
      } catch (InterruptedException e) {  
         e.printStackTrace();  
      }  
   }  
}
登入後複製

清單3:靜態代理類別 

/** 
 * 代理类,实现了代理接口。 
 */  
public class ProxySubject implements Subject {  
 //代理类持有一个委托类的对象引用  
 private Subject delegate;  
   
 public ProxySubject(Subject delegate) {  
  this.delegate = delegate;  
 }  
  
 /** 
  * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 
  *  
  * @param taskName 
  */  
 @Override  
 public void dealTask(String taskName) {  
  long stime = System.currentTimeMillis();   
  //将请求分派给委托类处理  
  delegate.dealTask(taskName);  
  long ftime = System.currentTimeMillis();   
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    
 }  
}
登入後複製

清單4:產生靜態代理類別工廠 

#
public class SubjectStaticFactory {  
 //客户类调用此工厂方法获得代理对象。  
 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
 public static Subject getInstance(){   
  return new ProxySubject(new RealSubject());  
 }  
}
登入後複製

清单5:客户类

public class Client1 {  
  
 public static void main(String[] args) {  
  Subject proxy = SubjectStaticFactory.getInstance();  
  proxy.dealTask("DBQueryTask");  
 }   
  
}
登入後複製

静态代理类优缺点

优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

缺点:

1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。

2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

三、动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

1、先看看与动态代理紧密关联的Java API。

1)java.lang.reflect.Proxy

这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

清单6:Proxy类的静态方法

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
static InvocationHandler getInvocationHandler(Object proxy)   
  
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
static Class getProxyClass(ClassLoader loader, Class[] interfaces)   
  
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类  
static boolean isProxyClass(Class cl)   
  
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例  
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
登入後複製

2)java.lang.reflect.InvocationHandler

这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

清单7:InvocationHandler的核心方法

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象  
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行  
Object invoke(Object proxy, Method method, Object[] args)
登入後複製

3)java.lang.ClassLoader

这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

每次生成动态代理类对象时都需要指定一个类装载器对象

2、动态代理实现步骤

具体步骤是:

a. 实现InvocationHandler接口创建自己的调用处理器

b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类

c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数

d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

清单8:分步骤实现动态代理

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发  
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用  
InvocationHandler handler = new InvocationHandlerImpl(..);   
  
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象  
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });   
  
// 通过反射从生成的类对象获得构造函数对象  
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });   
  
// 通过构造函数对象创建动态代理类实例  
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
登入後複製

Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。
清单9:简化后的动态代理实现

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发  
InvocationHandler handler = new InvocationHandlerImpl(..);   
  
// 通过 Proxy 直接创建动态代理类实例  
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,   
     new Class[] { Interface.class },  handler );
登入後複製

3、动态代理实现示例

清单10:创建自己的调用处理器

/** 
 * 动态代理类对应的调用处理程序类 
 */  
public class SubjectInvocationHandler implements InvocationHandler {  
   
 //代理类持有一个委托类的对象引用  
 private Object delegate;  
   
 public SubjectInvocationHandler(Object delegate) {  
  this.delegate = delegate;  
 }  
   
 @Override  
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  long stime = System.currentTimeMillis();   
  //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。  
  //因为示例程序没有返回值,所以这里忽略了返回值处理  
  method.invoke(delegate, args);  
  long ftime = System.currentTimeMillis();   
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    
  return null;  
 }  
}
登入後複製

清单11:生成动态代理对象的工厂,工厂方法列出了如何生成动态代理类对象的步骤。

/** 
 * 生成动态代理对象的工厂. 
 */  
public class DynProxyFactory {  
 //客户类调用此工厂方法获得代理对象。  
 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
 public static Subject getInstance(){   
  Subject delegate = new RealSubject();  
  InvocationHandler handler = new SubjectInvocationHandler(delegate);  
  Subject proxy = null;  
  proxy = (Subject)Proxy.newProxyInstance(  
    delegate.getClass().getClassLoader(),   
    delegate.getClass().getInterfaces(),   
    handler);  
  return proxy;  
 }  
}
登入後複製

清单12:动态代理客户类

public class Client {  
  
 public static void main(String[] args) {  
  
  Subject proxy = DynProxyFactory.getInstance();  
  proxy.dealTask("DBQueryTask");  
 }   
  
}
登入後複製

4、动态代理机制特点  

首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图: 

图2:动态代理类的继承关系 

靜態代理和動態代理的差別是什麼?

由圖可見,Proxy 類別是它的父類,這個規則適用於所有由 Proxy 建立的動態代理類別。而且該類別還實現了其所代理的一組接口,這就是為什麼它能夠被安全地類型轉換到其所代理的某接口的根本原因。 

接下來讓我們來了解代理類別實例的一些特性。每個實例都會關聯一個呼叫處理器對象,可以透過 Proxy 提供的靜態方法 getInvocationHandler 去取得代理類別實例的呼叫處理器對象。在代理類別實例上呼叫其代理的介面中所宣告的方法時,這些方法最終都會由呼叫處理器的invoke 方法執行,此外,值得注意的是,代理類別的根類別java.lang.Object 中有三個方法也同樣會被分派到呼叫處理器的invoke 方法執行,它們是hashCode,equals 和toString,可能的原因有:一是因為這些方法為public 且非final 類型,能夠被代理類別覆寫;二是因為這些方法往往呈現出一個類別的某種特徵屬性,具有一定的區分度,所以為了確保代理類別與委託類別對外的一致性,這三個方法也應該被分派到委託類別執行。當代理程式的一組介面有重複宣告的方法且該方法被呼叫時,代理類別總是從排在最前面的介面中取得方法物件並分派給呼叫處理器,而無論代理類別實例是否正在以該介面(或繼承於該介面的某子介面)的形式被外部引用,因為在代理類別內部無法區分其目前的被引用類型。 

接著來了解被代理程式的一組介面有哪些特色。首先,要注意不能有重複的接口,以避免動態代理類別程式碼生成時的編譯錯誤。其次,這些介面對於類別裝載器必須可見,否則類別裝載器將無法連結它們,將會導致類別定義失敗。再一次,需被代理的所有非 public 的介面必須在同一個套件中,否則代理類別產生也會失敗。最後,介面的數目不能超過 65535,這是 JVM 設定的限制。 

最後再來了解異常處理方面的特性。從呼叫處理器介面聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因為所有的異常都繼承於 Throwable 接口,但事實是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類別覆寫父類別或實作父介面的方法時,拋出的異常必須在原方法支援的異常列表之內。所以雖然呼叫處理器理論上講能夠,但實際上往往受限制,除非父介面中的方法支援拋 Throwable 異常。那麼如果在 invoke 方法中的確產生了介面方法聲明中不支援的異常,那將如何呢?放心,Java 動態代理類別已經為我們設計了解決方法:它將會拋出 UndeclaredThrowableException 例外。這個異常是一個 RuntimeException 類型,所以不會造成編譯錯誤。透過該異常的 getCause 方法,還可以獲得原來那個不支援的異常對象,以便於錯誤診斷。 

5、動態代理人的優點與美中不足 

優點: 

############################################################################

動態代理程式與靜態代理程式比較,最大的好處是介面中宣告的所有方法都會轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多​​的時候,我們可以進行彈性處理,而不需要像靜態代理那樣每一個方法進行中轉。在這個範例中看不出來,因為invoke方法體內嵌入了具體的外圍業務(記錄任務處理前後時間併計算時間差),實際上可以類似Spring AOP那樣配置外圍業務。

美中不足: 

誠然,Proxy 已經設計得非常優美,但還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援interface 代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態產生的代理類別的繼承關係圖,它們已經註定有一個共同的父類別叫做 Proxy。 Java 的繼承機制注定了這些動態代理類別們無法實作對 class 的動態代理,原因是多繼承在 Java 中本質上就行不通。 

有許多理由,人們可以否定對 class 代理的必要性,但同樣有一些理由,相信支持 class 動態代理會更美好。介面和類別的劃分,本來就不是很明顯,只是到了 Java 中才變得如此的細化。如果只從方法的聲明及是否被定義來考慮,有一種兩者的混合體,它的名字叫抽象類別。實現對抽象類別的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實作任何介面而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。                

想參考更多相關文章,請造訪PHP中文網! !

以上是靜態代理和動態代理的差別是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板