首頁 > 軟體

MyBatis攔截器的實現原理

2022-08-22 18:01:52

前言

Mybatis攔截器並不是每個物件裡面的方法都可以被攔截的。Mybatis攔截器只能攔截Executor、StatementHandler、ParameterHandler、ResultSetHandler四個類裡面的方法,這四個物件在建立的時候才會建立代理。

用途:實際工作中,可以使用Mybatis攔截器來做一些SQL許可權校驗、資料過濾、資料加密脫敏、SQL執行時間效能監控和告警等。

 1.使用方法

以在Spring中建立 StatementHandler.update()方法的攔截器為例:

@Component
@Order(1)
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),})
public class SqlValidateMybatisInterceptor extends PRSMybatisInterceptor {
 
    @Override
    protected Object before(Invocation invocation) throws Throwable {
        String sql="";
        Statement statement=(Statement) invocation.getArgs()[0];
        if(Proxy.isProxyClass(statement.getClass())){
            MetaObject metaObject= SystemMetaObject.forObject(statement);
            Object h=metaObject.getValue("h");
            if(h instanceof StatementLogger){
                RoutingStatementHandler rsh=(RoutingStatementHandler) invocation.getTarget();
                sql=rsh.getBoundSql().getSql();
            }else {
                PreparedStatementLogger psl=(PreparedStatementLogger) h;
                sql=psl.getPreparedStatement().toString();
            }
        }else{
            sql=statement.toString();
        }
        if(containsDelete(sql)&&!containsWhere(sql)){
            throw new SQLException("不能刪除整張表,sql:"+sql);
        }
        return null;
    }
 
    private boolean containsDelete(String sql){
        return sql.contains("delete")||sql.contains("DELETE");
    }
 
    private boolean containsWhere(String sql){
        return sql.contains("where")||sql.contains("WHERE");
    }
}
public class PRSMybatisInterceptor implements Interceptor {
 
    Boolean needBreak=false;
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result= before(invocation);
        if(needBreak){
            return result;
        }
        result= invocation.proceed();
        result=after(result,invocation);
        return result;
    }
 
    protected Object before(Invocation invocation) throws Throwable{
        return null;
    }
    protected Object after(Object result,Invocation invocation) throws Throwable{
        return result;
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

1. 自定義攔截器 實現 org.apache.ibatis.plugin.Interceptor 介面與其中的方法。在plugin方法中需要返回 return Plugin.wrap(o, this)。在intercept方法中可以實現攔截的業務邏輯,改方法的 引數 Invocation中有原始呼叫的 物件,方法和引數,可以對其任意處理。

2. 在自定義的攔截器上新增需要攔截的物件和方法,通過註解 org.apache.ibatis.plugin.Intercepts 新增。如範例程式碼所示:

Intercepts的值是一個簽名陣列,簽名中包含要攔截的 類,方法和引數。

2.MyBatis物件的建立

代理物件指的是:可以被攔截的4個類的範例。

代理物件建立時需要解析攔截器,從而利用JDK動態代理將攔截器的邏輯織入原始物件。

DefaultSqlSession中依賴Executor,如果新建的時候會建立executor

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    ...
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  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;
}

Executor中要用StatementHandler執行sql語句,StatementHandler是呼叫configuration.newStatementHandler()方法建立的。

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
 
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

StatementHandler依賴 parameterHandler 和 resultSetHandler,在構造 StatementHandler 時會呼叫一下方法建立這兩個 handler。

this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

3.代理物件的建立

3.1 攔截器的獲取

從物件的建立過程中可以看出 代理 物件的建立時通過 InterceptorChain.pluginAll() 方法建立的。

檢視 攔截器鏈 InterceptorChain 發現,其中的攔截器的新增是在 Configuration 中。因為攔截器被宣告為Bean了,所以在MyBatis初始化的時候,會掃描所有攔截器,新增到 InterceptorChain 中。

3.2 代理物件的建立

從上一步得知代理物件的建立是呼叫 Interceptor.pugin() 方法,然後呼叫 Plugin.wrap() 方法

Interceptor
@Override
public Object plugin(Object o) {
    return Plugin.wrap(o, this);
}

Plugin實現了 InvocationHandler 介面

 在 Plugin.wrap() 方法中會獲取當前攔截器的介面,生成動態代理。

4. 攔截器的執行過程

在動態代理中當代理物件呼叫方法時,會將方法的呼叫委託給 InvocationHandler,也就是 Plugin,

如下圖所示;

 在該方法中 獲取攔截器簽名中的方法,如果包含當前方法,則呼叫攔截方法,否則執行原方法的呼叫。

5. 攔截器的執行順序

攔截器的順序設定使用 Spring 中的 org.springframework.core.annotation.Order 註解設定。

order值大的攔截器先執行,order值大的在interceptors中越靠後,最後生成代理,所以先執行。

到此這篇關於MyBatis攔截器的實現原理的文章就介紹到這了,更多相關MyBatis攔截器 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com