拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑。 那么如果使用Mybatis对数据进行拦截,做一些满足自己需求的东西呢。今天我们就用这个实现一个数据的脱敏功能。
Interceptor接口
对于拦截器Mybatis
为我们提供了一个Interceptor
接口,通过实现该接口就可以定义我们自己的拦截器。首先我们先来看一下这个接口的定义:
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
可以看到,需要我们实现的方法有:
intercept
拦截器的处理逻辑
plugin
拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
setProperties
为插件设置properties
属性
需求实现
@Intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class DataMaskingInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取到返回结果
Object returnValue = invocation.proceed();
if (returnValue != null) {
// 对结果进行处理
if (returnValue instanceof ArrayList<?>) {
List<?> list = (ArrayList<?>) returnValue;
for (int index = 0; index < list.size(); index++) {
Object returnItem = list.get(index);
if (returnItem != null) {
Class<?> clazz = returnItem.getClass();
Type superType = clazz.getGenericSuperclass();
if (superType.getClass().isInstance(Object.class)) {
List<Field> fieldList = new ArrayList<>();
// 利用反射获取所有字段
ReflectionUtils.doWithFields(clazz, fieldList::add);
for (Field field : fieldList) {
// 获取结果类上的注解
TableField annotation = field.getAnnotation(TableField.class);
if (annotation != null) {
// 获取是否登记了脱敏 为什么要使用登记,而不使用注解呢?
String dataMaskingFlag = DataShieldHelper.getDataMasking();
if (!StringUtils.isEmpty(dataMaskingFlag)) {
Class<? extends DataMasking> dataMasking = annotation.dataMasking();
if (dataMasking != DataMasking.class) {
DataMasking instance = getInstance(dataMasking);
field.setAccessible(true);
String value = (String) field.get(returnItem);
value = instance.apply(value);
ReflectionUtils.setField(field, returnItem, value);
}
}
}
}
}
}
}
}
}
return returnValue;
}
/**
/* 固定写法 原理见 org.apache.ibatis.plugin.Plugin.wrap 方法
**/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
private <T> T getInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
return clazz.newInstance();
}
}
解释代码中为啥要使用DataShieldHelper
来实现脱敏登记,我们在项目开发过程中,经常会用到Java对象复用的情况,那么如果在某个字段上加上脱敏注解,会出现实现问题,我们不能对拦截器去判断哪些查询需要脱敏哪些不需要,那么怎么才能实现需要时才对数据进行脱敏,某些情况不需要呢,此时ThreadLocal
就能帮助到我们,ThreadLoca
l提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
/**
/* 线程工具类 主要用于登记脱敏操作
**/
public class DataShieldHelper {
private static final ThreadLocal<String> LOCAL_DATA_MASKING = new ThreadLocal();
public static void dataMasking(String symbol){
if(symbol==null || symbol.isEmpty()){
symbol = "*";
}
LOCAL_DATA_MASKING.set(symbol);
}
public static void dataMasking(){
dataMasking("*");
}
public static String getDataMasking(){
return LOCAL_DATA_MASKING.get();
}
public static void clearDataMasking(){
LOCAL_DATA_MASKING.remove();
}
}
使用@Intercepts
拦截ResultSetHandler
接口中参数类型为Statement
的handleResultSets
方法
ResultSetHandler 主要负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
拦截器的注册
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="config/jdbc.properties"></properties>
<typeAliases>
<package name="com.github.homeant.model" />
</typeAliases>
<plugins>
<plugin interceptor="com.github.homeant.mybatis.interceptor.DataMaskingInterceptor">
<!-- 属性 -->
<property name="prop1" value="prop1" />
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/github/homeant/mapper/UserMapper.xml" />
</mappers>
</configuration>
使用
public class Test{
@Test
public void test(){
// 脱敏登记
DataShieldHelper.dataMasking();
Optional<User> optional = userMapper.selectOn(user.getId());
// 取消
DataShieldHelper.clearDataMasking();
optional.ifPresent(r->{
log.debug("user:{}",r);
});
}
}
效果
可以看到,我们查询出来的数据已经脱敏了
2020-06-01 23:34:11.910 DEBUG 34392 --- [ main] c.g.h.d.s.mapper.UserMapper.selectOn : ==> Preparing: select id,username,password from t_user where id = ?
2020-06-01 23:34:11.910 DEBUG 34392 --- [ main] c.g.h.d.s.mapper.UserMapper.selectOn : ==> Parameters: 71(Integer)
2020-06-01 23:34:11.930 DEBUG 34392 --- [ main] c.g.h.d.s.mapper.UserMapper.selectOn : <== Total: 1
2020-06-01 23:34:11.940 DEBUG 34392 --- [ main] com.github.homeant.data.shield.DataTest : user:User(id=71, username=tom, password=p@***********67)
上述为简易版代码,如果需要,请前往GitHub进行下载