java動態代理的實現原理
1.理解下代理模式
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,
代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。
代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,
代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。
簡單的說就是,我們在訪問實際物件時,是通過代理物件來訪問的,
代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
在後面我會解釋這種間接性帶來的好處。
個人理解:代理類做一次訊息過濾,委託類負責實現具體的業務,而兩種實現了同一個介面
2.代理模式結構圖(圖片來自《大話設計模式》)
3.靜態代理
概念:由程式設計師建立或特定工具自動生成原始碼,也就是在編譯時
就已經將介面,被代理類,代理類等確定下來。在程式執行之前,代理類的.class檔案就已經生成
示例:
描述:假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。
這裡,班長就是代理學生上交班費,班長就是學生的代理
第一步:
建立一個Person介面。這個介面就是學生(被代理類),和班長(代理類)的公共介面,
他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。
/**
* 建立Person介面
* @author Gonjan
*/
public interface Person {
//上交班費
void giveMoney();
}
第二步: Student類實現Person介面。Student可以具體實施上交班費的動作
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override publicvoid giveMoney() { System.out.println(name + "上交班費50元"); } }
第三步:StudentsProxy類,這個類也實現了Person介面,但是還另外持有一個學生類物件,
由於實現了Peson介面,同時持有一個學生物件,
那麼他可以代理學生類物件執行上交班費(執行giveMoney()方法)行為。
/** * 學生代理類,也實現了Person介面,儲存一個學生實體,這樣既可以代理學生產生行為 * @author Gonjan * */ public class StudentsProxy implements Person{ //被代理的學生 Student stu; public StudentsProxy(Person stu) { // 只代理學生物件 if(stu.getClass() == Student.class) { this.stu = (Student)stu; } } //代理上交班費,呼叫被代理學生的上交班費行為 public void giveMoney() { stu.giveMoney(); } }
第四步:使用代理模式
public class StaticProxyTest { public static void main(String[] args) { //被代理的學生張三,他的班費上交有代理物件monitor(班長)完成 Person zhangsan = new Student("張三"); //生成代理物件,並將張三傳給代理物件 Person monitor = new StudentsProxy(zhangsan); //班長代理上交班費 monitor.giveMoney(); } }
這裡並沒有直接通過張三(被代理物件)來執行上交班費的行為,而是通過班長(代理物件)來代理執行了。這就是代理模式。
4.通過代理類處理額外事情
代理模式最主要的就是有一個公共介面(Person),一個具體的類(Student)也叫委託類,一個代理類(StudentsProxy),
代理類持有具體類的例項,代為執行具體類例項方法。
上面說到,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
這裡的間接性就是指不直接呼叫實際物件的方法,那麼我們在代理過程中就可以加上一些其他用途。
就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,
通過代理模式很輕鬆就能辦到:
public class StudentsProxy implements Person{ //被代理的學生 Student stu; public StudentsProxy(Person stu) { // 只代理學生物件 if(stu.getClass() == Student.class) { this.stu = (Student)stu; } } //代理上交班費,呼叫被代理學生的上交班費行為 public void giveMoney() { System.out.println("張三最近學習有進步!"); stu.giveMoney(); } }
總結(面向切面AOP也是基於這個原理)
可以看到,只需要在代理類中幫張三上交班費之前,執行其他操作就可以了。
這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切面程式設計(AOP),
我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。
這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
5.動態代理
概述:
代理類在程式執行時建立的代理方式被成為動態代理。
我們上面靜態代理的例子中,代理類(studentProxy)是自己定義好的,在程式執行之前就已經編譯完成。
然而動態代理,代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。
相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。
比如說,想要在每個代理的方法前都加上一個處理方法:
public void giveMoney() { //呼叫被代理方法前加入處理方法 beforeMethod(); stu.giveMoney(); }
總結:這裡只有一個giveMoney方法,就寫一次beforeMethod方法,
但是如果出了giveMonney還有很多其他的方法,
那就需要寫很多次beforeMethod方法,麻煩。那看看下面動態代理如何實現。
示例:
描述:在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面,
通過這個類和這個介面可以生成JDK動態代理類和動態代理物件。
第一步:建立一個InvocationHandler物件
//建立一個與代理物件相關聯的InvocationHandler InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
第二步:使用Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
第三步:獲得stuProxyClass中一個帶InvocationHandler引數的構造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
第四步:通過構造器constructor來建立一個動態例項stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
就此,一個動態代理物件就建立完畢,當然,上面四個步驟可以通過Proxy類的newProxyInstances方法來簡化:
//建立一個與代理物件相關聯的InvocationHandler InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu); //建立一個代理物件stuProxy,代理物件的每個執行方法都會替換執行Invocation中的invoke方法 Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
問題: 到這裡肯定都會很疑惑,這動態代理到底是如何執行的,是如何通過代理物件來執行被代理物件的方法的,
先不急,我們先看看一個簡單的完整的動態代理的例子。還是上面靜態代理的例子,班長需要幫學生代交班費。
第五步:
學習來源:
https://www.cnblogs.com/gonjan-blog/p/6685611.html
https://www.cnblogs.com/gonjan-blog/p/6685611.html