自己動(dòng)手編寫一個(gè)Mybatis插件之Mybatis脫敏插件

1. 前言
在日常開發(fā)中,身份證號(hào)、手機(jī)號(hào)、卡號(hào)、客戶號(hào)等個(gè)人信息都需要進(jìn)行數(shù)據(jù)脫敏。否則容易造成個(gè)人隱私泄露,客戶資料泄露,給不法分子可乘之機(jī)。但是數(shù)據(jù)脫敏不是把敏感信息隱藏起來(lái),而是看起來(lái)像真的一樣,實(shí)際上不能是真的。我以前的公司就因?yàn)椴恢匾暶撁?,一名員工在離職的時(shí)候通過后臺(tái)的導(dǎo)出功能導(dǎo)出了核心的客戶資料賣給了競(jìng)品,給公司造成了重大的損失。當(dāng)然這里有數(shù)據(jù)管理的原因,但是脫敏仍舊是不可忽略的一環(huán),脫敏可以從一定程度上保證數(shù)據(jù)的合規(guī)使用。下面就是一份經(jīng)過脫敏的數(shù)據(jù):

2. Mybatis 脫敏插件
最近在研究Mybatis的插件,所以考慮能不能在ORM中搞一搞脫敏,所以就嘗試了一下,這里分享一下思路。借此也分享一下Mybatis插件開發(fā)的思路。
2.1 Mybatis 插件接口
Mybatis中使用插件,需要實(shí)現(xiàn)接口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
}
}
這里其實(shí)最核心的是Object intercept(Invocation invocation)方法,這是我們需要實(shí)現(xiàn)的方法。
2.2 Invocation 對(duì)象
那么核心方法中的Invocation 是個(gè)什么概念呢?
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);
}
}
這個(gè)東西包含了四個(gè)概念:
- target 攔截的對(duì)象
- method 攔截target中的具體方法,也就是說Mybatis插件的粒度是精確到方法級(jí)別的。
- args 攔截到的參數(shù)。
- proceed 執(zhí)行被攔截到的方法,你可以在執(zhí)行的前后做一些事情。
2.3 攔截簽名
既然我們知道了Mybatis插件的粒度是精確到方法級(jí)別的,那么疑問來(lái)了,插件如何知道輪到它工作了呢?
所以Mybatis設(shè)計(jì)了簽名機(jī)制來(lái)解決這個(gè)問題,通過在插件接口上使用注解@Intercepts標(biāo)注來(lái)解決這個(gè)問題。
@Intercepts(@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
就像上面一樣,事實(shí)上就等于配置了一個(gè)Invocation。
2.4 插件的作用域
那么問題又來(lái)了,Mybatis插件能攔截哪些對(duì)象,或者說插件能在哪個(gè)生命周期階段起作用呢?它可以攔截以下四大對(duì)象:
- Executor 是SQL執(zhí)行器,包含了組裝參數(shù),組裝結(jié)果集到返回值以及執(zhí)行SQL的過程,粒度比較粗。
- StatementHandler 用來(lái)處理SQL的執(zhí)行過程,我們可以在這里重寫SQL非常常用。
- ParameterHandler 用來(lái)處理傳入SQL的參數(shù),我們可以重寫參數(shù)的處理規(guī)則。
- ResultSetHandler 用于處理結(jié)果集,我們可以重寫結(jié)果集的組裝規(guī)則。
你需要做的就是明確的你的業(yè)務(wù)需要在上面四個(gè)對(duì)象的哪個(gè)處理階段攔截處理即可。
2.5 MetaObject
Mybatis提供了一個(gè)工具類org.apache.ibatis.reflection.MetaObject。它通過反射來(lái)讀取和修改一些重要對(duì)象的屬性。我們可以利用它來(lái)處理四大對(duì)象的一些屬性,這是Mybatis插件開發(fā)的一個(gè)常用工具類。
- Object getValue(String name) 根據(jù)名稱獲取對(duì)象的屬性值,支持OGNL表達(dá)式。
- void setValue(String name, Object value) 設(shè)置某個(gè)屬性的值。
- Class<?> getSetterType(String name) 獲取setter方法的入?yún)㈩愋汀?/li>
- Class<?> getGetterType(String name) 獲取getter方法的返回值類型。
通常我們使用SystemMetaObject.forObject(Object object)來(lái)實(shí)例化MetaObject對(duì)象。你會(huì)在接下來(lái)的實(shí)戰(zhàn)DEMO中看到我使用它。
3. Mybatis 脫敏插件實(shí)戰(zhàn)
接下來(lái)我就把開頭的脫敏需求實(shí)現(xiàn)一下。首先需要對(duì)脫敏字段進(jìn)行標(biāo)記并確定使用的脫敏策略。
編寫脫敏函數(shù):
/**
* 具體策略的函數(shù)
* @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;
}
}
編寫脫敏字段的標(biāo)記注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
SensitiveStrategy strategy();
}
我們的返回對(duì)象中如果某個(gè)字段需要脫敏,只需要通過標(biāo)記就可以了。例如下面這樣:
@Data
public class UserInfo {
private static final long serialVersionUID = -8938650956516110149L;
private Long userId;
@Sensitive(strategy = SensitiveStrategy.USERNAME)
private String name;
private Integer age;
}
然后就是編寫插件了,我可以確定的是需要攔截的是ResultSetHandler對(duì)象的handleResultSets方法,我們只需要實(shí)現(xiàn)插件接口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();
// 對(duì)結(jié)果集脫敏
records.forEach(this::sensitive);
return records;
}
private void sensitive(Object source) {
// 拿到返回值類型
Class<?> sourceClass = source.getClass();
// 初始化返回值類型的 MetaObject
MetaObject metaObject = SystemMetaObject.forObject(source);
// 捕捉到屬性上的標(biāo)記注解 @Sensitive 并進(jìn)行對(duì)應(yīng)的脫敏處理
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);
// 獲取對(duì)應(yīng)的脫敏策略 并進(jìn)行脫敏
SensitiveStrategy type = annotation.strategy();
Object o = type.getDesensitizer().apply((String) value);
// 把脫敏后的值塞回去
metaObject.setValue(name, o);
}
}
}
然后配置脫敏插件使之生效:
@Bean
public SensitivePlugin sensitivePlugin(){
return new SensitivePlugin();
}
操作查詢獲得結(jié)果 UserInfo(userId=123123, name=李*龍, age=28) ,成功將指定字段進(jìn)行了脫敏。
補(bǔ)充一句,其實(shí)脫敏也可以在JSON序列化的時(shí)候進(jìn)行。
4. 總結(jié)
今天對(duì)編寫Mybatis插件的一些要點(diǎn)進(jìn)行了說明,同時(shí)根據(jù)說明實(shí)現(xiàn)了一個(gè)脫敏插件。但是請(qǐng)注意一定要熟悉四大對(duì)象的生命周期,否則自寫插件可能會(huì)造成意想不到的結(jié)果。
到此這篇關(guān)于自己動(dòng)手編寫一個(gè)Mybatis插件之Mybatis脫敏插件的文章就介紹到這了,更多相關(guān)Mybatis脫敏插件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Security權(quán)限想要細(xì)化到按鈕實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Spring?Security權(quán)限想要細(xì)化到按鈕實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
java編程實(shí)現(xiàn)基于UDP協(xié)議傳輸數(shù)據(jù)的方法
這篇文章主要介紹了java編程實(shí)現(xiàn)基于UDP協(xié)議傳輸數(shù)據(jù)的方法,較為詳細(xì)的分析了UDP協(xié)議的原理及Java編程實(shí)現(xiàn)數(shù)據(jù)傳輸客戶端與服務(wù)器端的相關(guān)技巧,需要的朋友可以參考下2015-11-11
java虛擬機(jī)之JVM調(diào)優(yōu)詳解
這篇文章主要介紹了java虛擬機(jī)之JVM調(diào)優(yōu)詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)Java虛擬機(jī)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
java容器類知識(shí)點(diǎn)詳細(xì)總結(jié)
這篇文章主要介紹了java容器類知識(shí)點(diǎn)詳細(xì)總結(jié),2019-06-06
Java正確實(shí)現(xiàn)一個(gè)單例設(shè)計(jì)模式的示例
今天小編就為大家分享一篇關(guān)于Java正確實(shí)現(xiàn)一個(gè)單例設(shè)計(jì)模式的示例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
Red?Hat?安裝JDK與IntelliJ?IDEA的詳細(xì)過程
YUM是基于Red Hat的Linux發(fā)行版的一個(gè)強(qiáng)大而用戶友好的包管理工具,這篇文章主要介紹了Red?Hat安裝JDK與IntelliJ IDEA,需要的朋友可以參考下2023-08-08
關(guān)于MVC的dao層、service層和controller層詳解
這篇文章主要介紹了關(guān)于MVC的dao層、service層和controller層詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
利用SpringBoot解決多個(gè)定時(shí)任務(wù)阻塞的問題
當(dāng)我們?cè)赟pring Boot應(yīng)用中使用多個(gè)定時(shí)任務(wù)時(shí),任務(wù)之間的阻塞可能是一個(gè)常見的問題,這可能會(huì)因任務(wù)之間的依賴、執(zhí)行時(shí)間過長(zhǎng)或資源爭(zhēng)用等原因而發(fā)生,本文讓我們深入探討如何利用Spring Boot來(lái)解決多個(gè)定時(shí)任務(wù)阻塞的問題,感興趣的小伙伴跟著小編一起來(lái)看看吧2024-01-01
Springboot中如何通過yml為實(shí)體類注入屬性
這篇文章主要介紹了Springboot中如何通過yml為實(shí)體類注入屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
idea?maven項(xiàng)目啟動(dòng)項(xiàng)目不編譯target?文件的問題及解決方法
代碼編輯器中無(wú)編譯錯(cuò)誤,通過maven 的clean 、compile、package進(jìn)行各種操作也都沒問題,但是單擊綠色箭頭運(yùn)行(默認(rèn)會(huì)先執(zhí)行IDE本身的Build操作)卻報(bào):程序包xxx不存在,這篇文章主要介紹了解決idea maven項(xiàng)目啟動(dòng)項(xiàng)目不編譯target文件問題,需要的朋友可以參考下2023-05-05

