Mybatis使用動態代理實現攔截器功能


 正文前先來一波福利推薦:

 福利一:

百萬年薪架構師視頻,該視頻可以學到很多東西,是本人花錢買的VIP課程,學習消化了一年,為了支持一下女朋友公眾號也方便大家學習,共享給大家。

福利二:

畢業答辯以及工作上各種答辯,平時積累了不少精品PPT,現在共享給大家,大大小小加起來有幾千套,總有適合你的一款,很多是網上是下載不到。

獲取方式:

微信關注 精品3分鍾 ,id為 jingpin3mins,關注后回復   百萬年薪架構師 ,精品收藏PPT  獲取雲盤鏈接,謝謝大家支持!

-----------------------正文開始---------------------------

 

1、背景介紹

  攔截器顧名思義為攔截某個功能的一個武器,在眾多框架中均有“攔截器”。這個Plugin有什么用呢?或者說攔截器有什么用呢?可以想想攔截器是怎么實現的。Plugin用到了Java中很重要的一個特性——動態代理。所以這個Plugin可以理解為,在調用一個方法時,我“攔截”其方法做一些我想讓它做的事(包括方法的前與后)。在Mybatis中可以攔截以下方法:

 

這正是mybatis中大名鼎鼎的四大對象;

 

2、源碼過程跟蹤,了解攔截器攔截過程以及原理

 1 //ParameterHandler 處理sql的參數對象
 2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 3     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 4     //包裝參數插件
 5     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  6     return parameterHandler;
 7 }
 8 
 9 //ResultSetHandler 處理sql的返回結果集
10 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
11                                             ResultHandler resultHandler, BoundSql boundSql) {
12     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
13     //包裝返回結果插件
14     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 15     return resultSetHandler;
16 }
17 
18 //StatementHandler 數據庫的處理對象
19 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
20     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
21     //包裝數據庫執行sql插件
22     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 23     return statementHandler;
24 }
25 
26 public Executor newExecutor(Transaction transaction) {
27     //創建Mybatis的執行器:Executor
28     return newExecutor(transaction, defaultExecutorType);
29 }
30 
31 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
32     executorType = executorType == null ? defaultExecutorType : executorType;
33     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
34     Executor executor;
35     //mybatis支持的三種執行器:batch、reuse、simple,其中默認支持的是simple
36     if (ExecutorType.BATCH == executorType) {
37         executor = new BatchExecutor(this, transaction);
38     } else if (ExecutorType.REUSE == executorType) {
39         executor = new ReuseExecutor(this, transaction);
40     } else {
       //默認為SimpleExecutor
41 executor = new SimpleExecutor(this, transaction); 42 }
      //如果開啟二級緩存 則對executor進行緩存包裝
43 if (cacheEnabled) { 44 executor = new CachingExecutor(executor); 45 } 46 //包裝執行器插件 47 executor = (Executor) interceptorChain.pluginAll(executor); 48 return executor; 49 }

 

  我們可以看到這四大對象 在創建的過程中 都調用了 (Executor) interceptorChain.pluginAll(Object target)  代碼,該代碼是怎么樣的呢  正是我們所說的攔截器鏈,將四大對象傳入到攔截器鏈進行處理 然后返回包裝后的 四大對象 如果我們在攔截器鏈中進行攔截處理 則實現了攔截技術;

下面我們看連接器鏈中的內容:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

//在pluginAll方法中 遍歷攔截器集 將傳入的target 也就是四大對象進行傳入 在interceptor中的Plugin方法中處理
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }

 

下面我們看一下interceptor對象的源代碼:

public interface Interceptor {

 Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

從源代碼看出 interceptor是一個接口 接口中 有三個方法 分別是 intercept plugin 和 setProperties;下面分別介紹着幾個方法;

我們自己寫插件或者攔截四大對象后 進行相應功能的添加就在要實現該接口,然后實現接口的三個方法;

 舉例實現該接口:

注意: 記住必須使用 注解的方式實現聲明攔截器攔截哪個類對象 原因在后面源碼中進行分析


@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) }) public class ExamplePlugin implements Interceptor { /* 此方法用於實現攔截邏輯 * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation) */ @Override
   public Object intercept(Invocation invocation) throws Throwable
   {

      doSomeThing();
      /* 注:此處實際上使用Invocation.proceed()方法完成interceptorChain鏈的遍歷調用(即執行所有注冊的Interceptor的intercept方法),到最終被代理對象的原始方法調用 */
      return invocation.proceed();
    }

/* 使用當前的這個攔截器實現對目標對象的代理(內部實現時Java的動態代理)
     * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
     */

    @Override
    public Object plugin(Object target)

    {

    /* 當目標類是StatementHandler類型時,才包裝目標類,不做無意義的代理 */
    return (target instanceof StatementHandler)?Plugin.wrap(target, this):target;
    }

/* 初始化Configuration時通過配置文件配置property傳遞參數給此方法並調用。
     * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
     */
    @Override
    public void setProperties(Properties properties) {  
        Iterator iterator = properties.keySet().iterator();
        while (iterator.hasNext()){
            String keyValue = String.valueOf(iterator.next()); System.out.println(properties.getProperty(keyValue)); } }

}

我們看點在plugin方法中 通過Plugin.wrap創建了代理對象 我們來看源代碼:看到了Plugin 類實現了 InvocationHandler 是不是感覺很熟悉 這就是我們上篇文章中講過的 動態代理中的 invocationHandel類

前面說了為什么自己實現攔截器類時 為什么必須使用注解的方式  因為在獲得數字簽名Map的方法中 存在使用反射獲得注解信息的方法

//獲得Interceptor注解,@Signature中的type(要攔截的類),method(攔截類的方法)和args(攔截器用於這些類中)

Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);

所以如果沒有添加注解方式 則會拋出 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName())

然后我們看一下wrap方法 該方法就是通過數據簽名 然后看數字簽名中的是否包含要攔截對象和方法  如果包含 則創建代理對象 返回代理對象

 
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; //此處的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();
    //解析被攔截對象的所有接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      //生成代理對象,Plugin對象為該代理對象的InvocationHandler
      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)) { //調用代理類所屬攔截器的intercept方法 return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //獲得Interceptor注解,@Signature中的type(要攔截的類),method(攔截類的方法)和args(攔截器用於這些類中)
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    //獲得注解type,method 和args生成一個signature數組
    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) {
      //獲得接口
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      //獲得父類
      type = type.getSuperclass();
    }
    //返回一個接口的數組
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
 
}

 到這里 代理對象以及中間對象invocationHandler對象已經創建完成  現在我們看一下 被代理對象 被代理對象就是 statementHandler 該接口的子類  這類的是 

SimpleStatementHandler 

由此可以看到該類實現了 statementHandler接口的方法。

通過看這里的源碼也可以看出 其實mysql的底層其實也是使用了 底層的jdbc來實現的!

public abstract class BaseStatementHandler implements StatementHandler {
 
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;
 
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;
 
  protected BoundSql boundSql;
 
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
 
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
 
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
 
    this.boundSql = boundSql;
 
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
  //獲取BoundSql
  public BoundSql getBoundSql() {
    return boundSql;
  }
  //獲取ParameterHandler
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }
 
  //准備語句
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //實例化Statement
      statement = instantiateStatement(connection);
      //設置超時
      setStatementTimeout(statement);
      //設置讀取條數
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
  //如何實例化Statement,交給子類去做
  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
 
  //設置超時,實際就是調用Statement.setQueryTimeout
  protected void setStatementTimeout(Statement stmt) throws SQLException {
    Integer timeout = mappedStatement.getTimeout();
    Integer defaultTimeout = configuration.getDefaultStatementTimeout();
    if (timeout != null) {
      stmt.setQueryTimeout(timeout);
    } else if (defaultTimeout != null) {
      stmt.setQueryTimeout(defaultTimeout);
    }
  }
  //設置讀取條數,其實就是調用Statement.setFetchSize
  protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
    }
  }
  //關閉Statement
  protected void closeStatement(Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      //ignore
    }
  }
    
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }
 
}

public class SimpleStatementHandler extends BaseStatementHandler {  
  
  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);  
  }  
  
  @Override  
  public int update(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    Object parameterObject = boundSql.getParameterObject();  
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
    int rows;  
    if (keyGenerator instanceof Jdbc3KeyGenerator) {  
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else if (keyGenerator instanceof SelectKeyGenerator) {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
    }  
    return rows;  
  }  
  
  @Override  
  public void batch(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.addBatch(sql);  
  }  
  
  @Override  
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.execute(sql);  
    return resultSetHandler.<E>handleResultSets(statement);  
  }  
  
  @Override  
  protected Statement instantiateStatement(Connection connection) throws SQLException {  
    if (mappedStatement.getResultSetType() != null) {  
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
    } else {  
      return connection.createStatement();  
    }  
  }  
  
  @Override  
  public void parameterize(Statement statement) throws SQLException {  
    // N/A  
  }  
  
}  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM