mybatis(6) - 自定義攔截器: Interceptor & plugin

能夠攔截哪些方法

默認狀況下,Mybatis容許使用插件來攔截的類及方法有:java

  • Executor:update、query、flushStatements、commit、rollback、getTransaction、close、isClosed。
    實現類:SimpleExecutor/BatchExecutor/ReuseExecutor/CachingExecutor
  • ParameterHandler:getParameterObject、setParameters。
    實現類:DefaultParameterHandler
  • ResultSetHandler:handleResultSets、handleOutputParameters。
    實現類:DefaultResultSetHandler
  • StatementHandler:prepare、parameterize、batch、update、query。
    實現類:CallableStatementHandler/PreparedStatementHandler/SimpleStatementHandler/RoutingStatementHandler

如何自定義插件

只需實現Interceptor接口,並指定要攔截的類、方法。
很重要的是實現的plugin方法,約定了如何包裝生成這個代理類。本類其實相似於代理的invocationhandler。通常Plugin.wrap(target,this)就好了緩存

在intercept方法中必定要調用Invocation的proceed方法並將返回值返回。mybatis

@Intercepts({
    @Signature(
        type=Executor.class,method="update",args={ MappedStatement.class,Object.class })
})
public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
       //自定義實現 do somethings 
       return invocation.proceed();
    }
    public Object plugin(Object target){
        return Plugin.wrap(target,this)
    }
    public void setProperties(Properties properties){
      //傳入配置項
      String size = properties.getProperty("size");
    }
}
<!-- mybatis-config.xml -->
<plugins>
    <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <!-- 這裏的配置項就傳入setProperties方法中 -->
        <property name="size" value="100">
    </plugin>
</plugins>

攔截器實現原理

若是瞭解Mybatis的攔截器實現原理,能夠在之後的工做中也可以使用該方法實現本身的攔截器app

插件包相關源碼

//攔截器接口,供外部實現,實現該接口就定義了一個插件
public interface Interceptor {
  //攔截方法,能夠將自定義邏輯寫在該方法中
  Object intercept(Invocation invocation) throws Throwable;
  //包裝成插件,通常Plugin.wrap(target,this)就好了
  Object plugin(Object target);
  //傳入自定義配置參數
  void setProperties(Properties properties);
}
攔截器上定義的註解
@Intercepts:攔截器註解,包括一個或多個@Signature,攔截的目標類信息
@Signature:攔截的目標類信息,包括type、method、args,一個@Intercepts中可包含多個@Signature

public class Invocation {
  private Object target;//目標對象
  private Method method;//調用方法
  private Object[] args;//方法形參列表
  //省略get和set方法
  //執行調用,基於動態代理,在Interceptor的intercept方法中必定要調用該方法
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}

在配置文件中定義的過濾器,都保存在Configuration類的interceptorChain中,這個類保存了mybatis的全部配置,interceptorChain類中保存中全部Interceptor集合組成的攔截器鏈。ide

//XMLConfigBuilder類中解析mybatis-config.xml 核心方法parseConfiguration(XNode root)
  pluginElement(root.evalNode("plugins"));//插件配置項

 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
    //遍歷 plugins的子節點plugin
    for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");//獲取interceptor屬性值
        Properties properties = child.getChildrenAsProperties();//獲取plugin屬性值
        //建立攔截器實例,這裏interceptor值也能夠是typeAlias註冊的簡名
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //設置屬性項
        interceptorInstance.setProperties(properties);
        //添加到interceptorChain中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  //Configuration類,添加攔截器
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

攔截的哪些接口

//SQL語句處理器
  public interface StatementHandler {
    //預備工做
    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
    //參數處理
    void parameterize(Statement statement) throws SQLException;
    //批量處理
    void batch(Statement statement)  throws SQLException;
    //更新處理
    int update(Statement statement) throws SQLException;
    //查詢處理
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  }

  //返回集處理器
  public interface ResultSetHandler {
    //處理返回結果
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    //處理輸出參數
    void handleOutputParameters(CallableStatement cs) throws SQLException;
  }

  //參數處理器
  public interface ParameterHandler {
     
    Object getParameterObject();

    void setParameters(PreparedStatement ps) throws SQLException;
  }

//執行器
public interface Executor {
    //更新
    int update(MappedStatement ms, Object parameter) throws SQLException;
    //查詢(先查緩存)
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
          ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    //查詢
    <E> List<E> query(MappedStatement ms, Object parameter, 
          RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    //查詢遊標
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) 
          throws SQLException;
    //刷新Statement
    List<BatchResult> flushStatements() throws SQLException;
    //提交事務
    void commit(boolean required) throws SQLException;
    //回滾事務
    void rollback(boolean required) throws SQLException;
    //建立緩存key
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject,RowBounds rowBounds, 
          BoundSql boundSql);
    //是否存在key
    boolean isCached(MappedStatement ms, CacheKey key);
    //清除本地緩存
    void clearLocalCache();
    //延遲加載
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, 
          Class<?> targetType);
    //獲取事務
    Transaction getTransaction();
    //關閉鏈接
    void close(boolean forceRollback);
    //是否關閉
    boolean isClosed();
    //設置Executor
    void setExecutorWrapper(Executor executor);
}

如何攔截這些接口

關鍵在於Configuration  在建立代理對象時執行的: interceptorChain.pluginAll方法。
最終回到了Plugin.wrap方法,會調用getAllInterfaces方法對signatureMap進行按@Signature的type進行篩選。有則構建代理對象,無則直接返回target源對象!
ui

//建立相應Handler時會將全部攔截器經過動態代理方式返回代理Handler
public class Configuration {

  //建立ParameterHandler(參數處理器)
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
        Object parameterObject, BoundSql boundSql) {
  // 根據指定Lang(默認RawLanguageDriver),建立ParameterHandler,將實際參數傳遞給JDBC語句
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(
        mappedStatement, parameterObject, boundSql);
    //返回代理實例
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  //建立ResultSetHandler(結果處理器)
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, 
     RowBounds rowBounds,ParameterHandler parameterHandler,
     ResultHandler resultHandler,BoundSql boundSql) {
    //默認使用DefaultResultSetHandler建立ResultSetHandler實例
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement,
       parameterHandler, resultHandler, boundSql, rowBounds);
    //返回代理實例
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  //建立StatementHandler(SQL語句處理器)
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //默認使用RoutingStatementHandler(路由做用)
    //建立指定StatementHandler實例(默認SimpleStatementHandler)
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, 
          parameterObject, rowBounds, resultHandler, boundSql);
    //返回代理實例
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  //建立Executor(執行器)
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //獲取executorType,默認是SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    //這一行感受有點多餘啊
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {  //批量執行
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {  //重用
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);//通用
    }
    if (cacheEnabled) {  //開啓緩存
      executor = new CachingExecutor(executor);
    }
    //返回代理實例
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}

Plugin源碼以下:this

//動態代理實現
public class Plugin implements InvocationHandler {
  private Object target;
  private Interceptor interceptor;//攔截器
  private Map<Class<?>, Set<Method>> signatureMap;//攔截目標類的目標方法

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  //包裝目標實例
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //目標類全部接口是否有signatureMap中定義的Class
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //若是攔截器中有定義攔截目標類中的方法時,就返回代理實例 
   if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    //沒有就返回目標實例
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //該方法須要攔截
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  //獲取攔截器上的SignatureMap
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());//重複定義的只生效一個
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        //獲取目標類中的指定方法
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {//獲取type上的全部接口
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {//這裏不判斷Method,只判斷Class<?>
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

總結

1.攔截器實現
Interceptor接口供插件實現,@Intercepts註解在插件實現上,表示這是一個插件類並配置將要攔截哪些類的哪些方法,@Signature定義將要攔截的方法信息,如類名、方法名、形參列表,Plugin類實現了InvocationHandler接口,是動態代理的具體實現,Invocation類包裝了攔截的目標實例,InterceptorChain保存全部攔截器。
2.如何實現攔截
建立目標實例,好比A a = new A();
Interceptor interceptor = new LogInterceptor();
將A b = (A)interceptor.plugin(a); 這裏b就是a的代理實例,在調用a中的save方法時,實際將調用interceptor的intercept方法,在該方法中必定要調用Invocation的proceed方法並將返回值返回。spa

相關文章
相關標籤/搜索