ByxAOP是一個基于JDK動態代理的簡易AOP框架,具有以下功能特性:
- 對目標物件的特定方法進行攔截和增強
- 支持靈活的攔截規則和自定義攔截規則
- 動態實作介面和批量實作介面方法
- 靈活的物件代理機制
專案地址:github 碼云
使用示例
首先來通過一個簡單例子快速了解ByxAOP,
假設我們有一個UserDao介面:
public interface UserDao
{
int listAll();
int listById(int id);
void deleteByName(String name);
}
UserDao介面有一個UserDaoImpl實作類:
public class UserDaoImpl implements UserDao
{
@Override
public int listAll()
{
System.out.println("正在執行listAll方法");
return 123;
}
@Override
public int listById(int id)
{
System.out.println("正在執行listById方法:id = " + id);
return 456;
}
@Override
public void deleteByName(String name)
{
System.out.println("正在執行deleteByName方法:name = " + name);
}
}
現在我們來完成一個需求:攔截UserDaoImpl中所有以list開頭的方法,即listAll和listById方法,攔程序序中列印出目標方法的一些相關資訊,使用ByxAOP來完成這個需求,只需要三個步驟:
-
第一步:創建一個方法攔截器(
MethodInterceptor):MethodInterceptor interceptor = (signature, targetMethod, params) -> { System.out.println("開始攔截" + signature.getName() + "方法"); System.out.println("原始引數:" + Arrays.toString(params)); Object ret = targetMethod.invoke(params); System.out.println("原始回傳值:" + ret); System.out.println("結束攔截" + signature.getName() + "方法"); return ret; };MethodInterceptor是ByxAOP中用來表示方法攔截器的核心介面,signature是目標方法簽名,targetMethod用于呼叫目標方法,params是傳遞給目標方法的引數,在interceptor中,首先列印了攔截開始資訊、目標方法的方法名和原始引數,然后呼叫了目標方法并獲取回傳值,最后列印了回傳值和攔截結束資訊,關于
MethodInterceptor介面及其相關方法和引數的詳細介紹,請看下文, -
第二步:創建一個方法匹配器(
MethodMatcher):MethodMatcher matcher = withPattern("list(.*)").andReturnType(int.class);MethodMatcher的構建使用了一種被稱為“流暢介面”的API設計風格,通過鏈式呼叫以及特定的命名來達到很好的可讀性,withPattern匹配方法名滿足指定正則運算式的方法,andReturnType則在上一個匹配器的基礎上增加了回傳值型別的條件,整個matcher表示匹配所有方法名以list開頭且回傳值為int型別的方法,MethodMatcher介面有很多預定義的實作類,通過組合這些類可以非常靈活地配置攔截規則,請看下文的詳細介紹, -
第三步:創建AOP代理物件:
UserDao userDao = proxy(new UserDaoImpl(), interceptor.when(matcher));proxy方法是AOP類中的一個靜態方法,用于創建AOP代理物件,第一個引數傳入目標物件(被增強的物件),第二個引數傳入方法攔截器,對于目標物件中的每一個方法呼叫,都會被該方法攔截器攔截,不過請注意,這里我們用interceptor.when(matcher)指定了攔截條件,由于match匹配以list開頭且回傳值為int型別的方法,所以只有滿足這個條件的方法才會被intercept攔截,
到這里,代理物件userDao就被創建出來了,現在讓我們來呼叫一下userDao中的各個方法:
userDao.listAll();
System.out.println();
userDao.listById(1001);
System.out.println();
userDao.deleteByName("XiaoMing");
控制臺輸出如下:
開始攔截listAll方法
原始引數:null
正在執行listAll方法
原始回傳值:123
結束攔截listAll方法
開始攔截listById方法
原始引數:[1001]
正在執行listById方法:id = 1001
原始回傳值:456
結束攔截listById方法
正在執行deleteByName方法:name = XiaoMing
從輸出結果可以看到,UserDaoImpl中的listAll方法和listById方法都被增強了,相應的資訊也列印出來了,而deleteByName方法沒有被增強,
下面將介紹ByxAOP的設計,
MethodInterceptor介面
首先來介紹一下MethodInterceptor介面,它是ByxAOP中的核心介面之一,
MethodInterceptor即方法攔截器,是對方法攔程序序的封裝,它的定義如下:
public interface MethodInterceptor
{
Object intercept(MethodSignature signature, Invokable targetMethod, Object[] params);
}
當目標方法被攔截時,intercept方法將會被呼叫,同時傳入一些目標方法的相關資訊,intercept方法的回傳值將作為代理方法的回傳值,
-
signature是目標方法簽名,用于在攔截時獲取目標方法的簽名資訊,signature是MethodSignature介面的實作類,MethodSignature介面包含下列方法:方法 說明 getName獲取方法名 getReturnType獲取回傳值型別 getParameterTypes獲取引數型別 getAnnotation獲取方法的指定注解 getAnnotation獲取方法上的指定注解 getAnnotations獲取方法上的所有注解 hasAnnotation方法是否被某個注解標注 getParameterAnnotations獲取方法引數上的注解 isPublic是否為public方法 isPrivate是否為private方法 isProtected是否為protected方法 -
targetMethod是目標方法呼叫器,用于在攔截時呼叫目標方法,向目標方法傳遞引數,以及獲取目標方法的回傳值
targetMethod是Invokable介面的實作類,Invokable介面的定義如下:public interface Invokable { Object invoke(Object... params); } -
params是傳遞給目標方法的原始引數,即我們呼叫代理物件的方法時傳遞的引數
用戶通過實作MethodInterceptor介面來定制方法攔程序序,在實作類的intercept方法中,用戶可以對目標方法的引數進行增強,也可以對目標方法的回傳值進行增強,還可以對目標方法拋出的例外進行處理,以及其它任何能想象到的攔截和增強操作,
還記得proxy方法嗎?
public static <T> T proxy(Object target, MethodInterceptor interceptor);
在呼叫AOP類中的proxy方法創建代理物件時,需要傳入一個MethodInterceptor的實作類,當我們呼叫代理物件的某個方法時,這個方法呼叫將會被代理到我們傳入的MethodInterceptor的intercept方法,同時proxy方法內部會把目標方法的相關資訊轉換成signature、targetMethod和params這三個引數,
注意,對于代理物件中的每個方法,都會被我們傳入的方法攔截器攔截,那么,如何讓方法攔截器只攔截某些特定的方法呢?
我們可以在方法攔截器的實作類中通過判斷signature資訊來選擇執行不同操作,就像下面這樣:
public class MyInterceptor implements MethodInterceptor
{
@Override
public Object intercept(MethodSignature signature, Invokable targetMethod, Object[] params)
{
if (signature.getName().startsWith("list") && signature.getReturnType() == int.class)
{
// 攔截以list開頭且回傳值型別為int的方法
}
// 對于其它方法,則直接呼叫目標方法,并回傳目標方法的回傳值
return targetMethod.invoke(params);
}
}
不過,在絕大多數情況下,我們并不需要通過手動判斷signature來攔截特定的方法,因為ByxAOP提供了很多預定義的MethodMatcher來表達方法匹配規則,只需要呼叫MethodInterceptor的when方法,并將某個MethodMatcher傳遞進去,就能得到只攔截特定方法的攔截器,
MethodMatcher介面
MethodMatcher是ByxAOP中的另一個核心介面,它封裝了方法匹配規則,其定義如下:
public interface MethodMatcher
{
boolean match(MethodSignature signature);
}
signature是方法簽名,MethodMatcher的實作類通過判斷方法簽名是否滿足某個條件來決定是否匹配該方法,如果匹配則回傳true,否則回傳false,
下面是所有預定義的MethodMatcher實作類,它們都是通過MethodMatcher的靜態工廠方法來獲取:
| 工廠方法 | 說明 |
|---|---|
all |
匹配所有方法 |
withName |
匹配指定名稱的方法 |
withPattern |
匹配方法名具有特定模式的方法 |
withReturnType |
匹配具有特定回傳值的方法 |
withParameterTypes |
匹配具有指定引數型別的方法 |
existInType |
匹配存在于另一個型別中的方法 |
hasAnnotation |
匹配被指定注解標注的方法 |
and |
匹配同時滿足兩個匹配條件的方法 |
or |
匹配至少滿足兩個匹配條件其中之一的方法 |
not |
匹配不滿足指定匹配結果的方法 |
通過組合這些自帶的MethodMatcher,基本可以表達任意的方法匹配規則,
下面這個matcher用于匹配所有方法名以list開頭且回傳值為int型別的方法:
MethodMatcher matcher = withPattern("list(.*)").andReturnType(int.class);
下面這個matcher用于匹配所有帶有Validate注解且引數型別為String和Integer的方法:
MethodMatcher matcher = hasAnnotation(Validate.class).andParameterTypes(String.class, Integer.class);
當然,如果用戶有自己的特殊需求,也可以自定義MethodMatcher的實作類,
多重AOP代理
將MethodInterceptor與MethodMatcher配合使用,還可以完成更復雜的需求,
還是上面UserDaoImpl的例子,假設我們既想攔截所有以list開頭的方法,又想攔截delete和insert方法,攔截list方法時需要列印出查詢日志,而攔截delete和list方法時需要進行事務管理,這個該如何實作呢?
MethodInterceptor interceptor1 = (signature, targetMethod, params) ->
{
// 列印日志的攔截器實作...
};
MethodInterceptor interceptor2 = (signature, targetMethod, params) ->
{
// 事務管理的攔截器實作...
};
// 匹配所有以list開頭的方法
MethodMatcher matcher1 = withPattern("list(.*)");
// 匹配delete方法和insert方法
MethodMatcher matcher2 = withName("delete").or(withName("insert"));
// 創建代理物件
UserDao userDao = proxy(new UserDaoImpl(), interceptor1.when(matcher1)
.then(interceptor2.when(matcher2)));
在上面的方法中,使用then將兩個MethodInterceptor連接起來,從而實作了多重AOP代理,
不僅僅是AOP
事實上,ByxAOP不僅僅是一個AOP框架,在AOP類中還有一個implement方法:
public static <T> T implement(Class<T> type, MethodInterceptor interceptor);
implement方法用于動態實作介面,這個方法配合MethodInterceptor中的delegateTo方法,可以實作很多炫酷的效果(你知道MyBatis中的Mapper介面是怎么實作的嗎?),關于這部分內容,請看:
- 動態實作介面
- 批量實作介面方法
- 優雅地創建配接器
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/263193.html
標籤:Java
上一篇:一個 Java 物件到底有多大?
