代理模式 -- SpringAOP的實現原理
網址連結:http://www.cnblogs.com/lcngu/p/5339555.html
代理模式UML圖:
代理模式中的角色:
1.抽象物件角色
聲明瞭目標類及代理類物件的共同介面,這樣在任何可以使用目標物件的地方都可以使用代理物件。
2.目標物件角色
定義了代理物件所代表的目標物件。
3.代理物件角色
代理物件內部含有目標物件的引用,從而可以在任何時候操作目標物件;代理物件和目標物件具有統一的介面,以便可以再任何時候替代目標物件。代理物件通常在客戶端呼叫傳遞給目標物件之前或者之後,執行某些操作,而非單純的將呼叫傳遞給目標物件。
1. 簡介
前段時間寫的java設計模式--代理模式,最近在看Spring Aop的時候,覺得於代理模式應該有密切的聯絡,於是決定了解下Spring Aop的實現原理。
說起AOP就不得不說下OOP了,OOP中引入封裝、繼承和多型性等概念來建立一種物件層次結構,用以模擬公共行為的一個集合。但是,如果我們需要為部分物件引入公共部分的時候,OOP就會引入大量重複的程式碼。例如:日誌功能。
AOP技術利用一種稱為“橫切”的技術,解剖封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,這樣就能減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如許可權認證、日誌、事務處理
2. 實現原理
前面在學習代理模式的時候,瞭解到代理模式分為動態代理和靜態代理。現在我們就以代理模式為基礎先實現我們自己的AOP框架,再來研究Spring的AOP的實現原理。
先以靜態代理實現,靜態代理關鍵是在代理物件和目標物件實現共同的介面,並且代理物件持有目標物件的引用。
3. 缺點:
- 冗餘,由於代理物件要實現與目標物件一致的介面,會產生過多的代理類。
- 不易維護,一旦介面增加方法,目標物件與代理物件都要進行修改。
公共介面程式碼:
public interface IHello { /** * 業務方法 * @param str */ void sayHello(String str); }
目標類程式碼:
public class Hello implements IHello{
@Override
public void sayHello(String str) {
System.out.println("hello "+str);
}
}
代理類程式碼,我們給它新增日誌記錄功能,在方法開始前後執行特定的方法,是不是和AOP特別像呢?
public class ProxyHello implements IHello{
private IHello hello;
public ProxyHello(IHello hello) {
super();
this.hello = hello;
}
@Override
public void sayHello(String str) {
Logger.start();//新增特定的方法
hello.sayHello(str);
Logger.end();
}
}
日誌類程式碼:
public class Logger {
public static void start(){
System.out.println(new Date()+ " say hello start...");
}
public static void end(){
System.out.println(new Date()+ " say hello end");
}
}
測試程式碼:
public class Test {
public static void main(String[] args) {
IHello hello = new ProxyHello(new Hello());//如果我們需要日誌功能,則使用代理類
//IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類
hello.sayHello("明天");
}
}
這樣我們就實現了一個最簡單的AOP,但是這樣會存在一個問題:如果我們像Hello這樣的類很多,那麼,我們是不是要去寫很多個HelloProxy這樣的類呢。其實也是一種很麻煩的事。在jdk1.3以後,jdk跟我們提供了一個API java.lang.reflect.InvocationHandler的類, 這個類可以讓我們在JVM呼叫某個類的方法時動態的為這些方法做些什麼事。下面我們就來實現動態代理的實現。
4. 動態代理:
動態代理是指動態的在記憶體中構建代理物件(需要我們指定要代理的目標物件實現的介面型別),即利用JDK的API生成指定介面的物件,也稱之為JDK代理或者介面代理。
動態代理實現主要是實現InvocationHandler,並且將目標物件注入到代理物件中,利用反射機制來執行目標物件的方法。
介面實現與靜態代理相同,代理類程式碼:
public class DynaProxyHello implements InvocationHandler{
private Object target;//目標物件
/**
* 通過反射來例項化一個目標物件的代理物件
* @param object
* @return
*/
public Object bind(Object object){
this.target = object;
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
Logger.start();//新增額外的方法
//通過反射機制來執行目標物件的方法
result = method.invoke(this.target, args);
Logger.end();
return result;
}
}
測試類程式碼:
public class DynaTest {
public static void main(String[] args) {
IHello hello = (IHello) new DynaProxyHello().bind(new Hello());//如果我們需要日誌功能,則使用代理類
//IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類
hello.sayHello("明天");
}
}
看完上面的程式碼可能和Spring AOP相比有一個問題,日誌類只能在方法前後列印,但是AOP應該是可以在滿足條件就可以執行,所有是否可以將DynaPoxyHello物件和日誌操作物件(Logger)解耦呢?
看下面程式碼實現,將將DynaPoxyHello物件和日誌操作物件(Logger)解耦:
我們要在被代理物件的方法前面或者後面去加上日誌操作程式碼(或者是其它操作的程式碼),那麼,我們可以抽象出一個介面,這個接口裡就只有兩個方法:一個是在被代理物件要執行方法之前執行的方法,我們取名為start,第二個方法就是在被代理物件執行方法之後執行的方法,我們取名為end。
Logger的介面:
public interface ILogger {
void start(Method method);
void end(Method method);
}
Logger的介面實現:
public class DLogger implements ILogger{
@Override
public void start(Method method) {
System.out.println(new Date()+ method.getName() + " say hello start...");
}
@Override
public void end(Method method) {
System.out.println(new Date()+ method.getName() + " say hello end");
}
}
動態代理類:
public class DynaProxyHello1 implements InvocationHandler{
//呼叫物件
private Object proxy;
//目標物件
private Object target;
public Object bind(Object target,Object proxy){
this.target=target;
this.proxy=proxy;
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
//反射得到Logger的例項
Class clazz = this.proxy.getClass();
//反射得到操作者的Start方法
Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class});
//反射執行start方法
start.invoke(this.proxy, new Object[]{this.proxy.getClass()});
//執行要處理物件的原本方法
method.invoke(this.target, args);
//反射得到操作者的end方法
Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class});
//反射執行end方法
end.invoke(this.proxy, new Object[]{method});
return result;
}
}
測試程式碼:
public class DynaTest1 {
public static void main(String[] args) {
IHello hello = (IHello) new DynaProxyHello1().bind(new Hello(),new DLogger());//如果我們需要日誌功能,則使用代理類
//IHello hello = new Hello();//如果我們不需要日誌功能則使用目標類
hello.sayHello("明天");
}
}
通過上面例子,可以發現通過動態代理和反射技術,已經基本實現了AOP的功能,如果我們只需要在方法執行前列印日誌,則可以不實現end()方法,這樣就可以控制列印的時機了。如果我們想讓指定的方法列印日誌,我們只需要在invoke()方法中加一個對method名字的判斷,method的名字可以寫在xml檔案中,這樣我們就可以實現以配置檔案進行解耦了,這樣我們就實現了一個簡單的spring aop框架。
5. 靜態代理和動態代理過程:https://www.cnblogs.com/V1haoge/p/5860749.html
1)首先我要說的就是介面,為什麼JDK的動態代理是基於介面實現的呢?
因為通過使用介面指向實現類例項的多型實現方式,可以有效的將具體的實現與呼叫之間解耦,便於後期修改與維護。
再具體的說就是我們在代理類中建立一個私有成員變數(private修飾),使用介面來指向實現類的物件(純種的多型體現,向上轉型的體現),然後在該代理類中的方法中使用這個建立的例項來呼叫實現類中的相應方法來完成業務邏輯功能。
這麼說起來,我之前說的“將具體實現類完全隱藏”就不怎麼正確了,可以改成,將具體實現類的細節向呼叫方完全隱藏(呼叫方呼叫的是代理類中的方法,而不是實現類中的方法)。
這就是面向介面程式設計,利用java的多型特性,實現程式程式碼的解耦。
(2)建立代理類的過程
如果你瞭解靜態代理,那麼你會發現動態代理的實現其實與靜態代理類似,都需要建立代理類,但是不同之處也很明顯,建立方式不同!
不同之處體現在靜態代理我們知根知底,我們知道要對哪個介面、哪個實現類來建立代理類,所以我們在編譯前就直接實現與實現類相同的介面,直接在實現的方法中呼叫實現類中的相應(同名)方法即可;而動態代理不同,我們不知道它什麼時候建立,也不知道要建立針對哪個介面、實現類的代理類(因為它是在執行時因需實時建立的)。
雖然二者建立時機不同,建立方式也不相同,但是原理是相同的,不同之處僅僅是:靜態代理可以直接編碼建立,而動態代理是利用反射機制來抽象出代理類的建立過程。
6. JDK和Cglib動態代理區別
JDK的動態代理依靠介面實現,如果有些類並沒有實現介面,則不能使用JDK動態代理,這就要使用cglib動態代理了。
Cglib動態代理
JDK的動態代理機制只能代理實現了介面的類,而沒有實現介面的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。
7. JDK動態代理步驟
我們總結下JDK動態代理的實現步驟:
第一步:宣告介面,JDK動態代理基於介面實現,所以介面必不可少(準備工作)
第二步:宣告一個真正的委託類,實現InvocationHandler介面,重寫invoke方法(準備工作)
第三步:呼叫Proxy的靜態方法newProxyInstance方法生成代理例項(生成例項時需要提供類載入器,我們可以使用介面類的載入器即可)
第四步:使用新生成的代理例項呼叫某個方法實現功能。(自動的呼叫代理例項中的invoke()方法進行代理操作)