AOP
引言
Spring 為解耦而生,其中AOP(面向切面程式設計)是很濃重的一筆。AOP(Aspect-Oriented-Programming)指一種程式設計範型,該範型以一種稱為切面(aspect)的語言構造為基礎,切面是一種新的模組化機制,用來描述分散在物件、類或方法中的橫切關注點(crosscutting)。
AOP相關概念
- 連線點(JoinPoint):程式執行到某個特定位置(如:某個方法呼叫前,呼叫後,方法丟擲異常),一個類或一段程式程式碼擁有一些具有邊界性質的特定點,這些程式碼中的特定點就是連線點。
- 切點(PointCut):如果連線點相當於資料中的記錄,那麼切點就相當於查詢條件,一個切點可以匹配多個連線點。SpringAop的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連線點。
- 增強(Advice):增強是織入到目標類連線點上的一段程式程式碼,Spring提供的增強介面都是帶方位的,如BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。,很多資料上將增強譯為“通知”,這明顯是詞不達意的。
說明:Advice在國內很多的書面資料上都被翻譯為“通知”,但這個翻譯無法表達其本質,有少量的讀物將這個詞翻譯為“增強”,這個翻譯是比較為準確的詮釋,我們通過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有的業務邏輯進行的一種增強,這個增強可以是前置增強,後置增強,返回後增強,丟擲異常時增強,和包圍型增強。
- 引介(Introduction):引介是一種特殊的增強,他為類新增一些屬性和方法,這樣,即使一個業務類原本沒有實現某個介面,通過引介功能,可以動態的為該業務類新增介面的實現邏輯,讓業務類稱為這個介面的實現類
- 織入(Weaving):織入是將增強新增到目標類具體連線點上的過程,AOP由三種織入方式:1 編譯期織入:需要特殊的Java編譯期(例如Aspect的ajc);2 裝載期織入:要求使用特殊的類載入器,在裝載類的時候對類進行增強,3 執行時織入:在執行時為目標類生成代理實現增強。Spring採用動態代理的方式事實現了執行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。
- 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連線點的定義。
原理
AOP是運用了動態代理。代理模式是GoF提出的23種設計模式中最為經典的模式之一,代理是物件的結構模式,他給某一個物件提供一個物件代理,並由代理控制對原物件的引用,簡單的說,代理物件可以完成比原物件更多的職責,當需要為原物件新增橫切關注功能時,就可以使用原物件的代理物件。我們在開啟Office系列的Word文件時,如果文件中有插圖,當文件剛載入時,文件中的插圖都只是一個虛框的佔位符,等真正使用者翻到某一頁要檢視該圖片時,才會真正載入這張圖片,這其實就是代理模式的使用,代替真正的虛框就是一個虛擬代理,Hibernate的load方法也是返回一個虛擬代理物件,等真正使用者需要訪問物件的屬性時,才向資料庫發出SQL語句。
按照代理建立初期,代理可以分為兩種:
- 靜態代理:由程式設計師建立或特定工具自動生成原始碼,再對其編譯,在程式執行前,代理類的.class檔案就已經存在了。
- 動態代理:在程式執行時,執行反射機制動態建立而成 另外還有CGLib動態代理,它是針對類實現的代理。
實現
JDK代理
靜態代理
下面用一個找槍手代考的例子演示代理模式的使用
package com.test.test2.interfaces;
/**
* @Author: Young
* @QQ: 403353323
* @Date: 2019/4/25 10:35
*/
public interface Candidate {
public void answerTheQuestion();
}
複製程式碼
package com.test.test2;
import com.test.test2.interfaces.Candidate;
/**
* @Author: Young
* @QQ: 403353323
* @Date: 2019/4/25 10:36
*/
public class LazyStudent implements Candidate {
private String name;
public LazyStudent(String name) {
this.name = name;
}
@Override
public void answerTheQuestion() {
// 懶學生只能寫出自己名字不會答題
System.out.println("姓名" + name);
}
}
複製程式碼
package com.test.test2;
import com.test.test2.interfaces.Candidate;
/**
* @Author: Young
* @QQ: 403353323
* @Date: 2019/4/25 10:40
*/
public class Gunman implements Candidate {
private Candidate target; //被代理的物件
public Gunman(Candidate target) {
this.target = target;
}
@Override
public void answerTheQuestion() {
// 搶手要寫上代考學生的名字
target.answerTheQuestion();
// 搶手幫助懶學生答題並交卷
System.out.println("奮筆疾書書寫正確答案");
System.out.println("交卷");
}
}
複製程式碼
@Test
public void testGunman(){
Candidate c = new Gunman(new LazyStudent("王小二"));
c.answerTheQuestion();
}
複製程式碼
觀察代理可以發現每一個代理類只能為一個介面服務,這樣一來程式開發中必然會產生很多的代理,而且,所有代理除了呼叫方法不一樣之外,其他操作都一樣,則此時肯定是重複程式碼,解決這一個問題最好的做法是通過一個代理類完成全部的代理功能,那麼此時就必須使用動態代理了。
動態代理
從JDK 1.3 開始,Java提供了動態技術,動態代理類的位元組碼在程式執行時由Java反射機制動態生成,無需程式設計師手工編寫它的原始碼。動態代理類不僅簡化了程式設計工作,而且提高了軟體系統的可擴充套件性,因為反射機制可以生成任意型別的動態代理。
java.lang.reflect
包中的Proxy類和InvocationHandler介面提供了生成動態代理的能力。
JDK動態代理中包含一個介面和一個類:
- InvocationHandler介面
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
複製程式碼
引數說明:
引數 | 說明 |
---|---|
Object proxy | 指被代理的物件 |
Method method | 要呼叫的方法 |
Object[] args | 方法呼叫時的引數 |
可以將InvocationHandler介面的子類想象成一個代理的最終操作類,替換掉ProxySubject。
- Proxy類:
- Proxy類是專門完成代理的操作類,可以通過此類為一個或多個介面動態生成實現類,此類提供瞭如下的操作方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
/**************
*具體內容略去**
**************/
}
複製程式碼
引數說明:
原始碼中的引數說明
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class to implement
* @param h the invocation handler to dispatch method invocations to
複製程式碼
引數 | 說明 |
---|---|
ClassLoader | 類載入器 |
Class<?>[] interfaces | 得到全部的介面 |
InvocationHandler h | 得到InvocationHandler介面的子類例項 |
Ps:在Proxy中類的newProxyInstance()
方法中需要一個ClassLoader類的例項,ClassLoader實際上對應的是類載入器。在Java中主要有三種類載入器:
- Bootstrap ClassLoader:此口載入器採用C++編寫,一般開發中是看不到的。
- Extendsion ClassLoader:用來進行擴充套件類的載入器,一般對應的是jre\lib\ext目錄中的類;
- APPClassLoader:(預設)載入classpath制定的類,是最常使用的一種載入器。
BookFacade.java
public interface BookFacade {
public void addBook();
}
複製程式碼
BookFacadeImpl.java
import net.battier.dao.BookFacade;
public class BookFacadeImpl implements BookFacade {
@Override
public void addBook() {
System.out.println("增加圖書方法。。。");
}
}
複製程式碼
BookFacadeProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK動態代理代理類
*/
public class BookFacadeProxy implements InvocationHandler {
private Object target;
/**
* 繫結委託物件並返回一個代理類
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理物件
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //要繫結介面(這是一個缺陷,cglib彌補了這一缺陷)
}
@Override
/**
* 呼叫方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("事物開始");
//執行方法
result=method.invoke(target, args);
System.out.println("事物結束");
return result;
}
}
複製程式碼
TestProxy.java
import net.battier.dao.BookFacade;
import net.battier.dao.impl.BookFacadeImpl;
import net.battier.proxy.BookFacadeProxy;
public class TestProxy {
public static void main(String[] args) {
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
bookProxy.addBook();
}
}
複製程式碼
說明:使用Java的動態代理有一個侷限性就是被代理的類必須實現介面(也就是說只能對該類所實現介面中定義的方法進行代理),雖然面向介面程式設計是每個優秀的Java程式設計師都知道的規則,但現實往往不盡人意,對於沒有實現介面的代理類如何為其生成代理類呢?繼承繼承是最經典的擴充套件已有程式碼能力的手段。CGLib代理就是這樣實現代理。
CGLib動態代理
CGLib採用非常底層的位元組碼生成技術,通過為一個類建立子類來生成代理類,它彌補了Java動態代理的不足,因此,Spring中的動態代理和CGLib都是建立代理的重要手段,對於實現了介面的類就用動態代理為其生成代理類,而沒有實現介面的類就用CGLib通過繼承方式為其建立代理類。但因為採用的是繼承,所以不能對final修飾的類進行代理。
intercept引數列表:
引數 | 說明 |
---|---|
Object obj | 代理的物件 |
Metho method | 表示攔截的方法 |
Object[] args | 陣列表示引數列表,基本資料型別需要傳入其包裝型別,如int-->Integer、long-Long、double-->Double |
MethodProxy methodProxy | 表示對方法的代理,invokeSuper方法表示對被代理物件方法的呼叫 |
package com.test.test2.proxy;
/**
* @Author: Young
* @QQ: 403353323
* @Date: 2019/5/3 21:32
*/
public class BookFacadeImpl1 {
public void addBook(){
System.out.println("增加圖書的普通方法");
}
}
複製程式碼
package com.test.test2.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* @Author: Young
* @QQ: 403353323
* @Date: 2019/5/3 21:34
*/
/**
* CGLib建立動態代理
*/
public class BookFacadeProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回撥方法
enhancer.setCallback(this);
//建立代理物件
return enhancer.create();
}
@Override
// 回撥方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("開始事務");
methodProxy.invokeSuper(obj, args);
System.out.println("事務結束");
return null;
}
}
複製程式碼
public class TestCglib {
public static void main(String[] args) {
BookFacadeCglib cglib=new BookFacadeCglib();
BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());
bookCglib.addBook();
}
}
複製程式碼