1. 依赖引入
在当下绝多数的工程中通常采用 MyBatis 作为数据交互框架,同类此处在项目中导入下述依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
2. 注解介绍
以往的文章中已经详细介绍过拦截器的使用,这里仅简要概述一下的 @Intercepts 注解的参数配置。
@Intercepts 中提供了注解参数 @Signature 用于配置拦截目标信息,@Signature 注解的配置参数如下:
| 方法 | 作用 |
|---|---|
| type | 指定目标方法所在的接口或类。 |
| method | 指定要拦截的目标方法的名称。 |
| args | 指定拦截目标方法的参数类型。 |
假设存在目标类 TargetClass,并包含了两个方法,具体内容如下:
public void TargetClass {
public void demo1(String s) {}
public void demo2(String s1, String s2) {}
}
若想要编写一个拦截器作用于上述 demo1() 方法,则 @Intercepts 注解的定义格式如下:
@Component
@Intercepts(
@Signature(
type = TargetClass.class,
method = "demo1",
args = { String.class }
)
)
public class TestInterceptor implements Interceptor { }
3. 拦截实现
基于上述描述,我们即可编辑拦截器实现 SQL 层面执行的跟踪编辑操作。
在 MyBatis 中 JDBC 的执行依托于 org.apache.ibatis.executor.Executor 接口,其包含如下接口方法:
| 接口方法 | 方法作用 |
|---|---|
| public Object plugin(Object target) | 配置开启拦截器插件,默认开启。 |
| Object intercept(Invocation var1) | 声明具体的拦截器业务逻辑 |
| void setProperties(Properties properties) | 用于接收配置文件标签里的外部参数。 |
以最常见的数据库查询为例,下述定义了作用于 query() 方法,当执行 <select> 标签查询语句时将会触发 intercept() 方法逻辑,可在其中执行通用数据操作业务。
@Component
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
)
)
@SuppressWarnings("rawtypes")
public class QueryInterceptor implements Interceptor {
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object params = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
// Get sql detail
Executor executor = (Executor) invocation.getTarget();
BoundSql boundSql = ms.getBoundSql(params); ;
CacheKey cacheKey = executor.createCacheKey(ms, params, rowBounds, boundSql);
// Handle target sql
SqlCommandType sqlType = ms.getSqlCommandType();
if (sqlType == SqlCommandType.SELECT) {
System.out.println("=========> Query sql: " + boundSql.getSql());
return executor.query(ms, params, rowBounds, resultHandler, cacheKey, boundSql);
}
return invocation.proceed();
}
}
如上所述 plugin() 方法提供了开关的效果,默认为 Plugin.wrap(target, this) 即开启拦截器插件。
而在工具库设计应用中,可搭配 @Value 读取外部配置从而实现动态的插件开关,当直接返回 target 则表明不启用插件,即不会触发 intercept() 所定义的事件。
public class TestIntercept implements Interceptor {
@Value("${switch.intercept:true}")
private Boolean switch;
@Override
public Object plugin(Object target) {
if (Boolean.FALSE.equals(switch)) {
// disable interceptor
return target;
}
return Plugin.wrap(target, this);
}
}