MyBatis攔截器的實現(xiàn)原理
前言
Mybatis攔截器并不是每個對象里面的方法都可以被攔截的。Mybatis攔截器只能攔截Executor、StatementHandler、ParameterHandler、ResultSetHandler四個類里面的方法,這四個對象在創(chuàng)建的時候才會創(chuàng)建代理。
用途:實際工作中,可以使用Mybatis攔截器來做一些SQL權(quán)限校驗、數(shù)據(jù)過濾、數(shù)據(jù)加密脫敏、SQL執(zhí)行時間性能監(jiān)控和告警等。
1.使用方法
以在Spring中創(chuàng)建 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. 自定義攔截器 實現(xiàn) org.apache.ibatis.plugin.Interceptor 接口與其中的方法。在plugin方法中需要返回 return Plugin.wrap(o, this)。在intercept方法中可以實現(xiàn)攔截的業(yè)務(wù)邏輯,改方法的 參數(shù) Invocation中有原始調(diào)用的 對象,方法和參數(shù),可以對其任意處理。
2. 在自定義的攔截器上添加需要攔截的對象和方法,通過注解 org.apache.ibatis.plugin.Intercepts 添加。如示例代碼所示:
Intercepts的值是一個簽名數(shù)組,簽名中包含要攔截的 類,方法和參數(shù)。
2.MyBatis對象的創(chuàng)建
代理對象指的是:可以被攔截的4個類的實例。
代理對象創(chuàng)建時需要解析攔截器,從而利用JDK動態(tài)代理將攔截器的邏輯織入原始對象。
DefaultSqlSession中依賴Executor,如果新建的時候會創(chuàng)建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執(zhí)行sql語句,StatementHandler是調(diào)用configuration.newStatementHandler()方法創(chuàng)建的。
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,在構(gòu)造 StatementHandler 時會調(diào)用一下方法創(chuàng)建這兩個 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.代理對象的創(chuàng)建
3.1 攔截器的獲取
從對象的創(chuàng)建過程中可以看出 代理 對象的創(chuàng)建時通過 InterceptorChain.pluginAll() 方法創(chuàng)建的。

查看 攔截器鏈 InterceptorChain 發(fā)現(xiàn),其中的攔截器的添加是在 Configuration 中。因為攔截器被聲明為Bean了,所以在MyBatis初始化的時候,會掃描所有攔截器,添加到 InterceptorChain 中。
3.2 代理對象的創(chuàng)建
從上一步得知代理對象的創(chuàng)建是調(diào)用 Interceptor.pugin() 方法,然后調(diào)用 Plugin.wrap() 方法
Interceptor
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}Plugin實現(xiàn)了 InvocationHandler 接口

在 Plugin.wrap() 方法中會獲取當(dāng)前攔截器的接口,生成動態(tài)代理。
4. 攔截器的執(zhí)行過程

在動態(tài)代理中當(dāng)代理對象調(diào)用方法時,會將方法的調(diào)用委托給 InvocationHandler,也就是 Plugin,
如下圖所示;

在該方法中 獲取攔截器簽名中的方法,如果包含當(dāng)前方法,則調(diào)用攔截方法,否則執(zhí)行原方法的調(diào)用。
5. 攔截器的執(zhí)行順序
攔截器的順序配置使用 Spring 中的 org.springframework.core.annotation.Order 注解配置。
order值大的攔截器先執(zhí)行,order值大的在interceptors中越靠后,最后生成代理,所以先執(zhí)行。


到此這篇關(guān)于MyBatis攔截器的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)MyBatis攔截器 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot + Kotlin整合MyBatis的方法教程
前幾天由于工作需要,便開始學(xué)習(xí)了kotlin,java基礎(chǔ)扎實學(xué)起來也還算比較快,對于kotlin這個編程語言自然是比java有趣一些,下面這篇文章主要給大家介紹了關(guān)于Spring Boot + Kotlin整合MyBatis的方法教程,需要的朋友可以參考下。2018-01-01
Intellij IDEA 關(guān)閉和開啟自動更新的提示?
這篇文章主要介紹了Intellij IDEA 關(guān)閉和開啟自動更新的提示操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Spring boot實現(xiàn)應(yīng)用打包部署的示例
本篇文章主要介紹了Spring boot實現(xiàn)應(yīng)用打包部署的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
SpringBoot?@RestControllerAdvice注解對返回值統(tǒng)一封裝的處理方法
這篇文章主要介紹了SpringBoot?@RestControllerAdvice注解對返回值統(tǒng)一封裝,使用@RestControllerAdvice對響應(yīng)進(jìn)行增強,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09

