1. 前言

在日常开发中,身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。否则容易造成个人隐私泄露,客户资料泄露,给不法分子可乘之机。但是数据脱敏不是把敏感信息隐藏起来,而是看起来像真的一样,实际上不能是真的。我以前的公司就因为不重视脱敏,一名员工在离职的时候通过后台的导出功能导出了核心的客户资料卖给了竞品,给公司造成了重大的损失。当然这里有数据管理的原因,但是脱敏仍旧是不可忽略的一环,脱敏可以从一定程度上保证数据的合规使用。下面就是一份经过脱敏的数据:

2. Mybatis 脱敏插件

最近在研究Mybatis的插件,所以考虑能不能在ORM中搞一搞脱敏,所以就尝试了一下,这里分享一下思路。借此也分享一下Mybatis插件开发的思路。

2.1 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
} }

这里其实最核心的是Object intercept(Invocation invocation)方法,这是我们需要实现的方法。

2.2 Invocation 对象

那么核心方法中的Invocation 是个什么概念呢?

public class Invocation {

  private final Object target;
private final Method method;
private final Object[] args; public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
} public Object getTarget() {
return target;
} public Method getMethod() {
return method;
} public Object[] getArgs() {
return args;
} public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
} }

这个东西包含了四个概念:

  • target 拦截的对象
  • method 拦截target中的具体方法,也就是说Mybatis插件的粒度是精确到方法级别的。
  • args 拦截到的参数。
  • proceed 执行被拦截到的方法,你可以在执行的前后做一些事情。

2.3 拦截签名

既然我们知道了Mybatis插件的粒度是精确到方法级别的,那么疑问来了,插件如何知道轮到它工作了呢?

所以Mybatis设计了签名机制来解决这个问题,通过在插件接口上使用注解@Intercepts标注来解决这个问题。

@Intercepts(@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}))

就像上面一样,事实上就等于配置了一个Invocation。

2.4 插件的作用域

那么问题又来了,Mybatis插件能拦截哪些对象,或者说插件能在哪个生命周期阶段起作用呢?它可以拦截以下四大对象:

  • Executor 是SQL执行器,包含了组装参数,组装结果集到返回值以及执行SQL的过程,粒度比较粗。
  • StatementHandler 用来处理SQL的执行过程,我们可以在这里重写SQL非常常用。
  • ParameterHandler 用来处理传入SQL的参数,我们可以重写参数的处理规则。
  • ResultSetHandler 用于处理结果集,我们可以重写结果集的组装规则。

你需要做的就是明确的你的业务需要在上面四个对象的哪个处理阶段拦截处理即可。

2.5 MetaObject

Mybatis提供了一个工具类org.apache.ibatis.reflection.MetaObject。它通过反射来读取和修改一些重要对象的属性。我们可以利用它来处理四大对象的一些属性,这是Mybatis插件开发的一个常用工具类。

  • Object getValue(String name) 根据名称获取对象的属性值,支持OGNL表达式。
  • void setValue(String name, Object value) 设置某个属性的值。
  • Class<?> getSetterType(String name) 获取setter方法的入参类型。
  • Class<?> getGetterType(String name) 获取getter方法的返回值类型。

通常我们使用SystemMetaObject.forObject(Object object)来实例化MetaObject对象。你会在接下来的实战DEMO中看到我使用它。

3. Mybatis 脱敏插件实战

接下来我就把开头的脱敏需求实现一下。首先需要对脱敏字段进行标记并确定使用的脱敏策略。

编写脱敏函数:

/**
* 具体策略的函数
* @author felord.cn
* @since 11:24
**/
public interface Desensitizer extends Function<String,String> { }

编写脱敏策略枚举:

/**
* 脱敏策略.
*
* @author felord.cn
* @since 11 :25
*/
public enum SensitiveStrategy {
/**
* Username sensitive strategy.
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* Id card sensitive type.
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
/**
* Phone sensitive type.
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), /**
* Address sensitive type.
*/
ADDRESS(s -> s.replaceAll("(\\S{8})\\S{4}(\\S*)\\S{4}", "$1****$2****")); private final Desensitizer desensitizer; SensitiveStrategy(Desensitizer desensitizer) {
this.desensitizer = desensitizer;
} /**
* Gets desensitizer.
*
* @return the desensitizer
*/
public Desensitizer getDesensitizer() {
return desensitizer;
}
}

编写脱敏字段的标记注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
SensitiveStrategy strategy();
}

我们的返回对象中如果某个字段需要脱敏,只需要通过标记就可以了。例如下面这样:

@Data
public class UserInfo { private static final long serialVersionUID = -8938650956516110149L;
private Long userId;
@Sensitive(strategy = SensitiveStrategy.USERNAME)
private String name;
private Integer age;
}

然后就是编写插件了,我可以确定的是需要拦截的是ResultSetHandler对象的handleResultSets方法,我们只需要实现插件接口Interceptor并添加签名就可以了。全部逻辑如下:

@Slf4j
@Intercepts(@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}))
public class SensitivePlugin implements Interceptor {
@SuppressWarnings("unchecked")
@Override
public Object intercept(Invocation invocation) throws Throwable {
List<Object> records = (List<Object>) invocation.proceed();
// 对结果集脱敏
records.forEach(this::sensitive);
return records;
} private void sensitive(Object source) {
// 拿到返回值类型
Class<?> sourceClass = source.getClass();
// 初始化返回值类型的 MetaObject
MetaObject metaObject = SystemMetaObject.forObject(source);
// 捕捉到属性上的标记注解 @Sensitive 并进行对应的脱敏处理
Stream.of(sourceClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Sensitive.class))
.forEach(field -> doSensitive(metaObject, field));
} private void doSensitive(MetaObject metaObject, Field field) {
// 拿到属性名
String name = field.getName();
// 获取属性值
Object value = metaObject.getValue(name);
// 只有字符串类型才能脱敏 而且不能为null
if (String.class == metaObject.getGetterType(name) && value != null) {
Sensitive annotation = field.getAnnotation(Sensitive.class);
// 获取对应的脱敏策略 并进行脱敏
SensitiveStrategy type = annotation.strategy();
Object o = type.getDesensitizer().apply((String) value);
// 把脱敏后的值塞回去
metaObject.setValue(name, o);
}
}
}

然后配置脱敏插件使之生效:

@Bean
public SensitivePlugin sensitivePlugin(){
return new SensitivePlugin();
}

操作查询获得结果 UserInfo(userId=123123, name=李*龙, age=28) ,成功将指定字段进行了脱敏。

补充一句,其实脱敏也可以在JSON序列化的时候进行。

4. 总结

今天对编写Mybatis插件的一些要点进行了说明,同时根据说明实现了一个脱敏插件。但是请注意一定要熟悉四大对象的生命周期,否则自写插件可能会造成意想不到的结果。插件可以关注:码农小胖哥 回复关键字 sensitive 进行获取。如果你觉得有用请无情的点赞。

关注公众号:Felordcn 获取更多资讯

个人博客:https://felord.cn

自己动手编写一个Mybatis插件:Mybatis脱敏插件的更多相关文章

  1. 自己动手编写一个VS插件(五)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续编写VisualStudio插件.这次我编写的插件叫DevAssist(意思是开发助手).在看了前面的文章之后你知 ...

  2. JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识

    JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...

  3. 自己动手编写一个VS插件(七)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续开发VS插件.今天在添加ATL控件时出现一个"未能返回新代码元素"的错误,如下图: 解决办法是 ...

  4. 自己动手编写一个VS插件(八)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 利用业余时间继续开发一个VS插件.我要开发的插件是一个代码库插件,主要是用于积累我平时要使用的代码.在之前我已经实现了 ...

  5. 自己动手编写一个VS插件(六)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 在上篇中我们已经实现了创建和显示一个工具栏出来,它的效果图是这样的: 现在我们实现一些简单功能,具体就是单击按钮弹出一 ...

  6. 如何自己编写一个easyui插件

    本文介绍如何通过参考1.4.2版本的progressbar的源码自己编写一个HelloWorld级别的easyui插件,以及如何拓展插件的功能. 有利于我们理解easyui插件的实现,以及了解如何对e ...

  7. 1-自己动手编写ArrayList集合

    学习集合的原理,这是一个很简单的代码,没有索引下标越界判断,异常处理等,不够健壮,后面会继续更新完善代码..... 只是入门学习一下 package my; /*** * 自己动手编写一个ArrayL ...

  8. 一个简单的MariaDB认证插件demo

    代码地址如下:http://www.demodashi.com/demo/13076.html 一.前言 众所周知(其实可能很多人不知道)MariaDB支持插件认证.在MariaDB中新建用户,常见的 ...

  9. 浅析MyBatis(三):聊一聊MyBatis的实用插件与自定义插件

    在前面的文章中,笔者详细介绍了 MyBatis 框架的底层框架与运行流程,并且在理解运行流程的基础上手写了一个自己的 MyBatis 框架.看完前两篇文章后,相信读者对 MyBatis 的偏底层原理和 ...

随机推荐

  1. Go Pentester - HTTP CLIENTS(2)

    Building an HTTP Client That Interacts with Shodan Shadon(URL:https://www.shodan.io/)  is the world' ...

  2. JVM——内存区域:运行时数据区域详解

    关注微信公众号:CodingTechWork,一起学习进步. 引言   我们经常会被问到一个问题是Java和C++有何区别?我们除了能回答一个是面向对象.一个是面向过程编程以外,我们还会从底层内存管理 ...

  3. postman之测试集

    简单点,说话的方式简单点 步骤1:测试全部通过,哈哈~ 开玩笑! 适用场景:多组数据测试,像排比句那样,有规律,比如姓名,性别,年龄.这时候的测试要求就是这些信息与返回体的结果做比较!! 步骤1:创建 ...

  4. [jvm] -- 常用内存参数配置篇

    新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ) Eden : from : to = 8 : 1 : 1 ( 可 ...

  5. Java面试必问:ThreadLocal终极篇 淦!

    点赞再看,养成习惯,微信搜一搜[敖丙]关注这个互联网苟且偷生的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系列 ...

  6. Day01_企业权限管理(SSM整合)

    学于黑马程序员和传智播客联合做的教学项目 感谢 黑马程序员官网 传智播客官网 个人根据教程的每天的工作进度的代码和资料 密码:cti5 b站在线视频 微信搜索"艺术行者",关注并回 ...

  7. Tkinter经典写法

    1.继承 tkinter.Frame 类,实现类的基本写法 2.创建主窗口及主窗口大小位置及标题 3.将需要添加的组件放入到类中进行创建, 继承的 Frame 类需要使用 master 参数作为父类的 ...

  8. 美团Leaf——全局序列生成器

    Leaf的Github地址: https://github.com/Meituan-Dianping/Leaf Leaf美团技术团队博客地址: https://tech.meituan.com/201 ...

  9. 探究:编程语言那么多,为什么偏偏是 C 语言成了大学的必修课?

    谁叫你不幸生在中国了? ——何祚庥(中国科学院院士) 这是一本给非计算机专业的大学生的C语言的书.“我不是学计算机的,为啥要学C语言?”这个问题每年在中华大地都会被问上几百万次.被问的对象可能是老师, ...

  10. EC R 87 div2 D. Multiset 线段树 树状数组 二分

    LINK:Multiset 主要点一下 二分和树状数组找第k大的做法. 线段树的做法是平凡的 开一个数组实现就能卡过. 考虑如树状数组何找第k大 二分+查询来判定是不优秀的. 考虑树状数组上倍增来做. ...