MyBatis之攔截器interceptor學習

  攔截器已經是各個開源軟件必不可少的功能。 在討論各種問題的時候也經常聽說這個對象被攔截了等等。那麼在JAVA的世界裏, 是怎麼實現攔截器的功能的呢 ?  要了解這些, 必須先從代理類(Proxy)說起,但是我們在這裏不打算從這裏介紹,我們直接上Mybatis的測試代碼。

    

Java代碼  收藏代碼
  1. public class PluginTest {  
  2.   
  3.   @Test  
  4.   public void mapPluginShouldInterceptGet() {  
  5.     Map map = new HashMap();  
  6.     map = (Map) new AlwaysMapPlugin().plugin(map);  
  7.     assertEquals("Always", map.get("Anything"));  
  8.   }  
  9.   
  10.   @Test  
  11.   public void shouldNotInterceptToString() {  
  12.     Map map = new HashMap();  
  13.     map = (Map) new AlwaysMapPlugin().plugin(map);  
  14.     assertFalse("Always".equals(map.toString()));  
  15.   }  
  16.   
  17.   @Intercepts({  
  18.       @Signature(type = Map.class, method = "get", args = {Object.class})})  
  19.   public static class AlwaysMapPlugin implements Interceptor {  
  20.     public Object intercept(Invocation invocation) throws Throwable {  
  21.       return "Always";  
  22.     }  
  23.   
  24.     public Object plugin(Object target) {  
  25.       return Plugin.wrap(target, this);  
  26.     }  
  27.   
  28.     public void setProperties(Properties properties) {  
  29.     }  
  30.   }  
  31.   
  32. }  

 

MyBatis 直接抽象了一個plugin的概念,中文意思就是“插頭”吧,很形象。我要對一個對象攔截,我就要把這個插頭插入到目標對象中:

Java代碼  收藏代碼
  1. map = (Map) new AlwaysMapPlugin().plugin(map);  

 

這裏生成了一個AlwaysMapPlugin,並調用plugin方法,把自己插入到Map的一個實例對象中;我們先不管plugin方法裏面到底做了什麼,我們只要知道它還是給我返回了一個Map的實例。

    

Java代碼  收藏代碼
  1. assertEquals("Always", map.get("Anything"));  

 

接着我們就調用了Map實例的get方法,我們納悶了,我們並沒有給Map實例中添加任何數據,但是卻能得到"Always”。

很神奇吧...

要解開這個神奇,還是要回到AlwaysMapPlugin().plugin(Object object)的方法上來。

Java代碼  收藏代碼
  1. public Object plugin(Object target) {  
  2.       return Plugin.wrap(target, this);  
  3. }  

直接委託給了Plugin類的一個靜態方法:

  

Java代碼  收藏代碼
  1. public static Object wrap(Object target, Interceptor interceptor) {  
  2.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
  3.     Class<?> type = target.getClass();  
  4.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
  5.     if (interfaces.length > 0) {  
  6.       return Proxy.newProxyInstance(  
  7.           type.getClassLoader(),  
  8.           interfaces,  
  9.           new Plugin(target, interceptor, signatureMap));  
  10.     }  
  11.     return target;  
  12.   }  

我們先拋開亂七八糟的邏輯,我們看到在滿足一定的條件的情況下,會返回一個Proxy.newProxyInstance類的一個實例。也就是說AlwaysMapPlugin().plugin(Object object)的方法返回的實例,是有可能和原來的實例不是同一個,而是原來實例的一個代理類。

Java代碼  收藏代碼
  1. public static Object newProxyInstance(ClassLoader loader,  
  2.                       Class<?>[] interfaces,  
  3.                       InvocationHandler h)  

 這個是Proxy.newProxyInstance方法的簽名,不明白的同志自己去了解下。

我們已經得到了目標對象的代理類,可是我們只想對目標對象的某幾個方法進行攔截, 是怎麼做到的呢?

這次我們把目光轉向我們的“插頭”吧。

   

Java代碼  收藏代碼
  1. @Intercepts({  
  2.       @Signature(type = Map.class, method = "get", args = {Object.class})})  
  3.   public static class AlwaysMapPlugin implements Interceptor {  
  4.     public Object intercept(Invocation invocation) throws Throwable {  
  5.       return "Always";  
  6.     }  
  7.   
  8.     public Object plugin(Object target) {  
  9.       return Plugin.wrap(target, this);  
  10.     }  
  11.   
  12.     public void setProperties(Properties properties) {  
  13.     }  
  14.   }  

 

這裏通過註解告訴我們,會對Map.get(Object obj)的方法進行攔截。而且在攔截之後,直接返回了Always, 這就是最終的原因了。

 

羅裏吧嗦一大堆,我們直接看MyBatis裏面抽象的幾個相關類吧:

 

1.  Plugin  類:也就是我們說的“插板”類, 繼承了InvocationHandler 接口,主要的職責是將目標對象、攔截器組裝成一個新的代理類。 簡單的代碼如下:

Java代碼  收藏代碼
  1. public class Plugin implements InvocationHandler {  
  2.   
  3.   private Object target; // 目標對象  
  4.   private Interceptor interceptor; //攔截器對象  
  5.   private Map<Class<?>, Set<Method>> signatureMap; //目標對象的方法簽名  
  6.   
  7.   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {  
  8.     this.target = target;  
  9.     this.interceptor = interceptor;  
  10.     this.signatureMap = signatureMap;  
  11.   }  
  12.   
  13.   public static Object wrap(Object target, Interceptor interceptor) {  
  14.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
  15.     Class<?> type = target.getClass();  
  16.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
  17.     if (interfaces.length > 0) {  
  18.       return Proxy.newProxyInstance(  // 返回一個目標對象的代理類  
  19.           type.getClassLoader(),  
  20.           interfaces,  
  21.           new Plugin(target, interceptor, signatureMap));  
  22.     }  
  23.     return target;  
  24.   }  
  25.   
  26.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  27.     try {  
  28.       Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
  29.       if (methods != null && methods.contains(method)) {  
  30.         return interceptor.intercept(new Invocation(target, method, args));//對目標對象中的某個方法進行攔截,將相關的實現給到了攔截器的實現類  
  31.       }  
  32.       return method.invoke(target, args);  
  33.     } catch (Exception e) {  
  34.       throw ExceptionUtil.unwrapThrowable(e);  
  35.     }  
  36.   }  

 2. Interceptor 攔截器的接口:

 

Java代碼  收藏代碼
  1. public interface Interceptor {  
  2.   
  3.   Object intercept(Invocation invocation) throws Throwable; // 對方法進行攔截的抽象方法  
  4.   
  5.   Object plugin(Object target); //把攔截器插入到目標對象的方法  
  6.   
  7.   void setProperties(Properties properties);  
  8.   
  9. }  

 3. Invocation 真正對目標類方法的攔截的實現, 這裏沒有什麼說的。 值得一提的是, 如果我們想實現類似spring的攔截器,比如說前置通知、後置通知、環繞通知等,應該是可以在這裏做文章的。

Java代碼  收藏代碼
  1. public class Invocation {  
  2.   
  3.   private Object target;  
  4.   private Method method;  
  5.   private Object[] args;  
  6.   
  7.   public Invocation(Object target, Method method, Object[] args) {  
  8.     this.target = target;  
  9.     this.method = method;  
  10.     this.args = args;  
  11.   }  
  12.   
  13.   public Object getTarget() {  
  14.     return target;  
  15.   }  
  16.   
  17.   public Method getMethod() {  
  18.     return method;  
  19.   }  
  20.   
  21.   public Object[] getArgs() {  
  22.     return args;  
  23.   }  
  24.   
  25.   public Object proceed() throws InvocationTargetException, IllegalAccessException {  
  26.     return method.invoke(target, args);  
  27.   }  
  28.   
  29. }  

 4. 搞定。

 

總結:其實攔截器還是很簡單的, 只需要熟識了Proxy類、InvocationHandler接口, 基本都能寫出來,只是寫的好與壞,是不是低耦合、高內聚的代碼。類的命名是不是讓別人一看就懂,這些都決定着一個人水平的高低;

攔截器使用的場景: 比如說 權限、日誌記錄、緩存等等;

 

先分享到這裏.....


原文地址:http://robert-wei.iteye.com/blog/1630132

發佈了106 篇原創文章 · 獲贊 21 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章