代理模式是常見的設計模式之一,意圖在為指定物件提供一種代理以控制對這個物件的訪問,Java中的代理分為動態代理和靜態代理,動態代理在Java中的應用比較廣泛,比如Spring的AOP實作、遠程RPC呼叫等,靜態代理和動態代理的最大區別就是代理類是JVM啟動之前還是之后生成,本文會介紹Java的靜態代理和動態代理,以及二者之間的對比,重點是介紹動態代理的原理及其實作,
代理模式
代理模式的定義:為其他物件提供一種代理以控制對這個物件的訪問,在某些情況下,一個物件不適合或者不能直接參考另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用,比如說:要訪問的物件在遠程的機器上,在面向物件系統中,有些物件由于某些原因(比如物件創建開銷很大,或者某些操作需要安全控制,或者需要行程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此物件時加上一個對此物件的訪問層,

代理的組成
代理由以下三部分角色組成:
- 抽象角色:通過介面或抽象類宣告真實角色實作的業務方法,
- 代理角色:實作抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實作抽象方法,并可以附加自己的操作,
- 真實角色:實作抽象角色,定義真實角色所要實作的業務邏輯,供代理角色呼叫,
代理的優點
- 職責清晰:真實的角色就是實作實際業務的邏輯,不用關系非業務的邏輯(如事務管理),
- 隔離作用:代理物件可以在客戶端和目標物件之間起到中介作用,目標物件不直接暴露給客戶端,從而實作隔離目標物件的作用
- 高可擴展性:代理物件可以對目標物件進行靈活的擴展,
代理的例子
我們用一個加載并顯示圖片的例子來解釋代理的作業原理,圖片存在磁盤上,每次IO會花費比較多的事件,如果我們需要頻繁的顯示圖片,每次都從磁盤讀取會花費比較長的時間,我們通過一個代理來快取圖片,只有第一次讀取圖片的時候才從磁盤讀取,之后都從快取中讀取,原始碼示例如下:
import java.util.*;
interface Image {
public void displayImage();
}
//on System A
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
public void displayImage() {
System.out.println("Displaying " + filename);
}
}
//on System B
class ProxyImage implements Image {
private String filename;
private Image image;
public ProxyImage(String filename) {
this.filename = filename;
}
public void displayImage() {
if(image == null)
image = new RealImage(filename);
image.displayImage();
}
}
class ProxyExample {
public static void main(String[] args) {
Image image1 = new ProxyImage("HiRes_10MB_Photo1");
Image image2 = new ProxyImage("HiRes_10MB_Photo2");
image1.displayImage(); // loading necessary
image2.displayImage(); // loading necessary
}
}
靜態代理
靜態代理需要在程式中定義兩個類:目標物件類和代理物件類,為了保證二者行為的一致性,目標物件和代理物件實作了相同的介面,代理類的資訊在程式運行之前就已經確定,代理物件中會包含目標物件的參考,
舉例說明靜態代理的使用: 假設我們有一個介面方法用于計算員工工資,有一個實作類實作了具體的邏輯,如果我們需要給計算員工工資的邏輯添加日志應該怎么辦呢?直接在計算工資的實作邏輯里面添加會導致引入非業務邏輯,不符合規范,這個時候我們就可以引入一個日志代理,在計算工資前后輸出相關的日志資訊,
- 計算員工工資的介面定義如下:
public interface Employee {
double calculateSalary(int id);
}
- 計算員工工資的實作類如下:
public class EmployeeImpl {
public double calculateSalary(int id){
return 100;
}
}
- 帶有日志的代理類的實作如下:
public class EmployeeLogProxy implements Employee {
//代理類需要包含一個目標類的物件參考
private EmployeeImpl employee;
//并提供一個帶參的構造方法用于指定代理哪個物件
public EmployeeProxyImpl(EmployeeImpl employee){
this.employee = employee;
}
public double calculateSalary(int id) {
//在呼叫目標類的calculateSalary方法之前記錄日志
System.out.println("當前正在計算員工: " + id + "的稅后工資");
double salary = employee.calculateSalary(id);
System.out.println("計算員工: " + id + "的稅后工資結束");
// 在呼叫目標類方法之后記錄日志
return salary;
}
}
動態代理
動態代理的代理物件類在程式運行時被創建,而靜態代理物件類則是在程式編譯期就確定好的,這是二者最大的不同之處,動態代理的優勢再于不需要開發者手工寫很多代理類,比如上面的例子中,如果再來一個Manager類計算工資的邏輯需要日志,那么我們就需要新建一個ManagerLogProxy來代理物件,如果需要代理的物件很多,那么需要寫的代理類也會很多,
而使用動態代理則沒有這種問題,一種型別的代理只需要寫一次,就可以適用于所有的代理物件,比如上文中的Employee和Manager,二者只需要抽象一個計算薪資相關的介面,就可以使用同一套動態代理邏輯實作代理,
動態代理示例
下面我們使用上文中的Employee和Manager計算薪資的邏輯來展示動態代理的用法,
介面的抽象
我們知道Employee和Manager都有計算薪資的邏輯,而且需要對計算薪資的邏輯進行日志記錄,所以我們需要抽象一個計算薪資的介面:
public interface SalaryCalculator {
double calculateSalary(int id);
}
介面的實作
public class EmployeeSalaryCalculator implements SalaryCalculator{
public double calculateSalary(int id){
return 100;
}
}
public class ManagerSalaryCalculator implements SalaryCalculator{
public double calculateSalary(int id){
return 1000000;
}
}
創建動態代理的InvocationHandler
public class SalaryLogProxy implements InvocationHandler {
private SalaryCalculator calculator;
public SalaryLogProxy(SalaryCalculator calculator) {
this.calculator = calculator;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("--------------begin-------------");
Object invoke = method.invoke(subject, args);
System.out.println("--------------end-------------");
return invoke;
}
}
創建代理物件
public class Main {
public static void main(String[] args) {
SalaryCalculator calculator = new ManagerSalaryCalculator();
InvocationHandler calculatorProxy = new SalaryLogProxy(subject);
SalaryCalculator proxyInstance = (SalaryCalculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), calculatorProxy);
proxyInstance.calculateSalary(1);
}
}
動態代理原始碼分析
動態代理的流程如下圖所示,可以看到動態代理中包含以下內容:
- 目標物件:我們需要代理的物件,對應上文中的
new ManagerSalaryCalculator(), - 介面:目標物件和代理物件需要共同提供的方法,對應上文中的
SalaryCalculator, - Proxy代理:用于生成代理物件類,
- 代理物件類:通過代理和對應的引數得到的代理物件,
- 類加載器:用于加載代理物件類的類加載器,對應上文中的
calculatorProxy.getClass().getClassLoader(),

Proxy.newProxyInstance
動態代理的關鍵代碼就是Proxy.newProxyInstance(classLoader, interfaces, handler).
- 可以看到
Proxy.newProxyInstance一共做了兩件事情:1.獲取代理物件類的建構式,2:根據建構式實體化代理物件,
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null : Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
// 獲取代理物件類的建構式,里面就包含了代理物件類的構建和加載
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
// 根據建構式生成代理實體.
return newProxyInstance(caller, cons, h);
}
代理物件類
通過查看原始碼,我們可以發現代理物件類都extend了Proxy類并實作了指定介面中的方法,由于java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實作類的代理,只支持介面的代理,
我是御狐神,歡迎大家關注我的微信公眾號

本文最先發布至微信公眾號,著作權所有,禁止轉載!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/300336.html
標籤:其他
下一篇:java MD5 加密
