多么希望压得我喘不过气的不是生活,而是一个成熟御姐
MyBatis 插件
前置知识
最好了解或回顾一下以下知识点:
- 动态代理模式
- 责任链模式
为啥要了解这两个设计模式?
插件的原理:责任链模式 + JDK动态代理(接口,代理对象、代理类 implement InvocationHandler)
如果不了解也没啥问题,学会咋用就行(反正我也没写插件原理,太耗时间了,下次一定
),就像下面这样:
Interceptor
先上个官网地址:配置_MyBatis中文网
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用,自定义拦截器需要的接口:org.apache.ibatis.plugin.Interceptor
默认情况下,MyBatis 允许使用插件来拦截的对象有以下:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) SQL执行器,用于执行增删改操作
- ParameterHandler (getParameterObject, setParameters) 处理 SQL 的参数对象
- ResultSetHandler (handleResultSets, handleOutputParameters) 处理 SQL 返回的结果集
- StatementHandler (prepare, parameterize, batch, update, query) 拦截 SQL 语句构建的处理
了解 mybatis 插件的接口
如果你要自定义 mybatis 插件,你必须要实现 org.apache.ibatis.plugin.Interceptor
接口,我们看下这个接口的方法:
public interface Interceptor {
//拦截逻辑
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
-
intercept
方法: 如果自定义插件实现 Interceptor 覆盖 intercept 方法,这个方法是一个核心方法,里面的参数 Invocation 对象,可以通过这个对象反射调度原来对象的方法。插件的核心:其实会拦截四个接口的子对象,拦截以后会进入到 intercept 方法中进行业务的处理,Invocation 对象可以获取到四个接口的具体
-
plugin
方法: 参数 target 指的就是代理对象。它的作用:把拦截的 target 对象变成一个代理对象,并且返回它
动态代理规则:当一个代理对象,执行某个方法的时候,就进入代理类的 invoke 方法中
-
setProperties
方法: 允许 plugin 在注册的时候,配置插件需要的参数,这个参数可以在 mybatis 的核心配置文件中注册插件的时候,一起配置到文件中,如下:<plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
这四个接口分别是什么?
- Executor:SQL执行器,用于执行增删改操作
- ParameterHandler:处理 SQL 的参数对象
- ResultSetHandler:处理 SQL 返回的结果集
- StatementHandler :拦截 SQL 语句构建的处理
插件小案例
数据脱敏插件
什么是数据脱敏?
举个例子,查询用户信息:
脱敏前:
- 张三达美
脱敏后:
- 张 * * 美
其实就是对敏感数据做点处理
用图片举个例子,这是数据脱敏前:
这个是数据脱敏后:
好的,简单了解了数据脱敏后,就开始使用 mybatis 插件特性来完成数据脱敏的实现。
思路
先理一下思路:
首先我们需要对数据的结果集进行拦截,也就是说需要拦截这个接口的方法
得到数据返回的结果集后,对结果集转换成 List 进行遍历脱敏
脱敏的时候我们还需要判断一下哪些字段需要进行脱敏,这里我们定义注解来标识需要脱敏的字段(
在这个注解里面可以定义一些脱敏策略,比如对手机号的脱敏规则、身份证的脱敏规则等等
)遍历的时候通过反射,获取所有属性
我这里的脱敏有三个条件
- 1、必须带有 脱敏标识注解
- 2、必须是 String 类型
- 3、不得为空,这里直接使用工具类来判断 StringUtils.isEmpty()
脱敏条件达成后,就获取该注解上的
脱敏策略
,根据脱敏策略
对属性进行脱敏将脱敏后的结果重新设置到属性上
就这样,大功告成!
先看一下我这的最终目录结构:
编码
首先新建一个 TongMinPlugin 类
这个就是我们的脱敏插件主要类
继承了 Interceptor
这个拦截器接口,这里会让你实现 intercept(Invocation invocation)
这个方法,拦截逻辑主要也是在这里编写的
package com.yuan.plugin;
@Component //需要放入到 ioc 容器中拦截器才会生效哦
//指定要拦截的目标方法的注解
//<h1>1、这个就是指定需要拦截的方法,这里我们指定拦截返回结果集的方法</h1>
@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args= Statement.class))
public class TongMinPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//<h1>2、得到数据返回的结果集,对结果集转换成 List<Object> 进行遍历脱敏</h1>
List<Object> records = (List<Object>) invocation.proceed();
//遍历数据 调用 tuoMin 这个方法
records.forEach(this::tuoMin);
return records;
}
private void tuoMin(Object source) {
}
}
到这里需要先定义一下注解和策略:
//这里的Function是jdk8新特性,自己了解一哈子
//提示:传入一个函数执行
public interface Desensitizer extends Function<String,String> {
}
//这里定义脱敏策略
public enum TuoMinStrategy {
/*手机号*/
PHONE(s->s.replaceAll("^(\\\\d{3})\\\\d{4}(\\\\d{4})$","$1****$2")),
/*用户名 匹配中文全部替换*/
USERNAME(s->s.replaceAll("[\\u4e00-\\u9fa5]","*"));
private final Desensitizer desensitizer;
TuoMinStrategy(Desensitizer d) {
desensitizer = d;
}
public Desensitizer getDesensitizer() {
return desensitizer;
}
}
然后,我们的注解:
//作用于属性上
@Target(ElementType.FIELD)
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface TuoMin {
/**
* 脱敏策略
* @return
*/
TuoMinStrategy strategy();
}
最后,给需要脱敏的字段加上注解:
@Data
public class User implements Serializable {
private static final long serialVersionUID = 415277636701148394L;
private Integer id;
@TuoMin(strategy = TuoMinStrategy.USERNAME) //加上注解并指定脱敏策略
private String username;
private String password;
}
接下来就可以开始写脱敏方法的逻辑了
private void tuoMin(Object source) {
Class<?> sourceClass = source.getClass();
MetaObject metaObject = SystemMetaObject.forObject(source);//这个东东是 mybatis 提供的,通过这个玩意我们可以得到属性的值
//使用 stream 流
/**
* 1、得到所有属性
* 2、筛选出带有 TuoMin.class 注解的属性
* 3、调用doTuoMin方法将这些属性脱敏
*/
Stream.of(sourceClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(TuoMin.class))
.forEach(field->doTuoMin(metaObject,field));
}
哈哈,其实里面还有一层方法哒!
private void doTuoMin(MetaObject metaObject, Field field) {
String name = field.getName();
Object value = metaObject.getValue(name);//根据属性名得到属性值
//脱敏条件:必须为String类型,值不等于空
if (String.class == metaObject.getGetterType(name) && !StringUtils.isEmpty(value)) {
//获取脱敏策略
TuoMin tuoMin = field.getAnnotation(TuoMin.class);
TuoMinStrategy type = tuoMin.strategy();
//调用策略正则表达式脱敏,得到结果
Object apply = type.getDesensitizer().apply((String) value);
//将脱敏后的结果重新设置到属性上
metaObject.setValue(name,apply);
}
}
到这里,我们的小型数据脱敏插件就已经编码完毕了,接下来进行测试:
原本的数据:
拦截脱敏后的数据:
嗯,看来测试的没毛病