一、前言
1月份已經過了一半多,天氣回暖了許多,今天就來學習一下mybatis插件相關內容,可能mybatis插件使用得很少,但是對於某一些業務場景,plugin可以幫助解決一些問題,就比如脫敏問題,我們在實際中,我們需要導出Excel,但是並不希望用戶信息完整的展示出來,所以我們可以脫敏,姓名只顯示楊楠、 151*1234等等,所以plugin可以結合相應的業務場景進行開發
二、mybatis plugin介紹
2.1 四大核心對象
- ParameterHandler:用來處理傳入SQL的參數,我們可以重寫參數的處理規則。
getParameterObject, setParameters
- ResultSetHandler:用於處理結果集,我們可以重寫結果集的組裝規則。 handleResultSets, handleOutputParameters 複製代碼
- StatementHandler:用來處理SQL的執行過程,我們可以在這裏重寫SQL非常常用。
prepare, parameterize, batch, update, query
- Executor:是SQL執行器,包含了組裝參數,組裝結果集到返回值以及執行SQL的過程,粒度比較粗。
update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
2.2 Interceptor
想要開發mybatis插件,我們只需要實現org.apache.ibatis.plugin.Interceptor接口,以下爲Interceptor接口的結構:
public interface Interceptor {
//代理對象每次調用的方法,就是要進行攔截的時候要執行的方法。在這個方法裏面做我們自定義的邏輯處理
Object intercept(Invocation invocation) throws Throwable;
/**
*生成target的代理對象,通過調用Plugin.wrap(target,this)來生成
*/
Object plugin(Object target);
//用於在Mybatis配置文件中指定一些屬性的,註冊當前攔截器的時候可以設置一些屬性
void setProperties(Properties properties);
}
昨天我們剛剛學習了JDK代理,下面我們一起來看看Invocation對象
public class Invocation {
//需要攔截的對象
private final Object target;
//攔截target中的具體方法,也就是說Mybatis插件的粒度是精確到方法級別的。
private final Method method;
//攔截到的參數。
private final Object[] args;
……getter/setter……
//proceed 執行被攔截到的方法,你可以在執行的前後做一些事情。
public Object proceed() throws InvocationTargetException, IllegalAccessException {
//執行目標類的目標方法
return method.invoke(target, args);
}
}
以上的proceed方法(method.invoke(target, args))是否似曾相識,因爲昨天在寫代理的時候,JDK代理用到了該方法,執行被代理類的方法,表示指定目標類的目標方法
2.3 攔截簽名
因爲我們在Interceptor中提到了Invocation,主要用於獲取攔截對象的信息,並且執行相應的方法,但是我們應該如何獲取Invocation對象?
@Intercepts(@Signature(type = ResultSetHandler.class, //攔截返回值
method = "handleResultSets", //攔截的方法
args = {Statement.class}))//參數
這樣其實就創建了一個Invocation對象
@Signature參數
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
//定義攔截的類 Executor、ParameterHandler、StatementHandler、ResultSetHandler當中的一個
Class<?> type();
//在定義攔截類的基礎之上,在定義攔截的方法,主要是看type裏面取哪一個攔截的類,取該類當中的方法
String method();
//在定義攔截方法的基礎之上在定義攔截的方法對應的參數,
Class<?>[] args();
}
舉個例子:
比如,我們需要攔截Executor類的update方法,思路如下:
- 定義Interceptor的實現類
public class MybatisPlugin implements Interceptor{}
- 增加@Intercepts註解,標識需要攔截的類、方法、方法參數
/**
* 插件簽名,告訴當前的插件用來攔截哪個對象的哪種方法
*/
@Intercepts(
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class,Object.class}
)
)
@Slf4j
public class MybatisPlugin implements Interceptor {}
@Signature註解:
1.type: 表示所需要攔截的類爲Executor
2.method:爲Executor類中的update方法
3.args: 這個對應update的的參數,如下:
int update(MappedStatement ms, Object parameter)
- 實現intercept、plugin、setProperties方法
/**
* 攔截目標對象的目標方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("攔截目標對象:{}",invocation.getTarget());
Object proceed = invocation.proceed();
log.info("intercept攔截到的返回值:{}",proceed);
return proceed;
}
/**
* 包裝目標對象 爲目標對象創建代理對象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
log.info("將要包裝的目標對象:{}",target);
//創建代理對象
return Plugin.wrap(target,this);
}
/**
* 獲取配置文件的屬性
* @param properties
*/
@Override
public void setProperties(Properties properties) {
log.info("獲取配置文件參數");
}
- 注入plugin
@Configuration
public class MybatisPluginBean {
@Bean
public MybatisPlugin mybaticPlugin(){
return new MybatisPlugin();
}
}
2.4 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對象。
三、mybatis脫敏插件
- 1、首先,定義函數接口,用於存儲脫敏策略
- 2、定義註解,用於標識需要脫敏的屬性
- 3、實現Interceptor接口,用於處理脫敏操作
- 4、註冊插件
3.1 定義函數接口
JDK8開始,加入了函數式編程接口,之前我們給對象傳遞的都是值,但是現在我們可以傳遞表達式,我們只需要繼承Function<String,String>
/**
* 函數式接口
*/
public interface Declassified extends Function<String,String> {
}
3.2 定義脫敏策略
脫敏策略,主要是約定名字應該如何脫敏,將楊羽茉轉換爲楊茉*
/**
* 脫敏的策略
*/
public enum SensitiveStrategy{
//定義名稱脫敏處理表達式
NAME(s -> s.replaceAll("(\\S)\\S(\\S*)","$1*$2"));
private Declassified declassified;
//注入脫敏函數式接口
SensitiveStrategy(Declassified declassified){
this.declassified = declassified;
}
//獲取脫敏函數式接口
public Declassified getDeclassified() {
return declassified;
}
}
寫到這裏,我們進行拓展,學習s.replaceAll()的使用方式:
replaceAll(): 給定的參數 replacement 替換字符串所有匹配給定的正則表達式的子字符串
public String replaceAll(String regex, String replacement)
regex -- 匹配此字符串的正則表達式。
replacement -- 用來替換每個匹配項的字符串。
複製代碼
@Test
void contextLoads() {
this.strProcess("楊羽茉");
}
public void strProcess(String name){
//意思:將name轉換爲$1*$2方式,也就是將中間的字符替換爲a*b
String s = name.replaceAll("(\\S)\\S*(\\S)", "$1*$2");
System.out.println("轉換的字符串:"+s);
}
輸出結果:
轉換的字符串:楊*茉
再來一個例子,脫敏手機號碼:
public void strProcess(String phone){
//將手機號碼轉換爲131***2172的方式
//(\\d{3})代表$1顯示前3位
// \\d* 代表中間部分替換爲***
//(\\d{4}) 代表$2顯示後三位
String s = phone.replaceAll("(\\d{3})\\d*(\\d{4})", "$1***$2");
System.out.println("轉換的字符串:"+s);
}
3.2 定義脫敏註解
/**
* 脫敏註解:標識需要脫敏的字段,並且指定具體的脫敏策略
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
//脫敏策略
SensitiveStrategy strategy();
}
3.4 實現Interceptor接口
/**
* mybatis插件
*/
@Intercepts(
@Signature(
type = ResultSetHandler.class, //表示需要攔截的是返回值
method = "handleResultSets", //表示需要攔截的方法
args = {Statement.class} //表示需要攔截方法的參數
)
)
public class MybatisPlugin implements Interceptor {
private Logger log = LoggerFactory.getLogger(MybatisPlugin.class);
/**
* 攔截方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//執行目標方法
List<Object> records = (List<Object>) invocation.proceed();
records.forEach(this::sensitive);
return records;
}
/**
* 過濾@Sensitive註解
* @param source
*/
private void sensitive(Object source){
//獲取返回值類型
Class<?> sourceClass = source.getClass();
//獲取返回值的metaObject:通過反射來讀取和修改一些重要對象的屬性
MetaObject metaObject = SystemMetaObject.forObject(source);
//我們在攔截的時候,需要過濾沒有@Sensitive註解的屬性,如果有註解,在doSensitive中進行脫敏操作
Stream.of(sourceClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Sensitive.class))
.forEach(field -> doSensitive(metaObject,field));
}
/**
* 脫敏操作
* @param metaObject
* @param field
*/
private void doSensitive(MetaObject metaObject, Field field){
//獲取屬性名
String name = field.getName();
//獲取屬性值
Object value = metaObject.getValue(name);
//只有字符串纔可以脫敏
if(String.class == metaObject.getGetterType(name) && value != null){
//獲取自定義註解
Sensitive annotation = field.getAnnotation(Sensitive.class);
//獲取自定義註解的參數
SensitiveStrategy strategy = annotation.strategy();
//脫敏操作
String apply = strategy.getDeclassified().apply((String) value);
//將脫敏後的數據放回返回值中
metaObject.setValue(name,apply);
}
}
/**
* 生成代理對象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
/**
* 設置屬性
* @param properties
*/
@Override
public void setProperties(Properties properties) {
}
}
3.5 註冊插件
@Configuration
public class MybatisPluginConfig {
@Bean
public MybatisPlugin mybatisPlugin(){
return new MybatisPlugin();
}
}
使用註解
@Sensitive(strategy = SensitiveStrategy.NAME)
private String name;
結果
{
userId=1,
name=管*員
}
Mybatis plugin已經完成,明天開始要認真的開始系統化的學習netty相關內容,明天寫一篇netty整合websocket,晚安!
作者:Yangzinan
鏈接:https://juejin.cn/post/6919846755995484168
來源:掘金