一、前言
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} ))@Slf4jpublic 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 */@Overridepublic Object intercept(Invocation invocation) throws Throwable { log.info("攔截目標物件:{}",invocation.getTarget()); Object proceed = invocation.proceed(); log.info("intercept攔截到的返回值:{}",proceed); return proceed;}/** * 包裝目標物件 為目標物件建立代理物件 * @param target * @return */@Overridepublic Object plugin(Object target) { log.info("將要包裝的目標物件:{}",target); //建立代理物件 return Plugin.wrap(target,this);}/** * 獲取配置檔案的屬性 * @param properties */@Overridepublic void setProperties(Properties properties) { log.info("獲取配置檔案引數");}
注入plugin@Configurationpublic 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 註冊外掛@Configurationpublic class MybatisPluginConfig { @Bean public MybatisPlugin mybatisPlugin(){ return new MybatisPlugin(); }}
使用註解
@Sensitive(strategy = SensitiveStrategy.NAME)private String name;
結果
{ userId=1, name=管*員}
Mybatis plugin已經完成,明天開始要認真的開始系統化的學習netty相關內容,明天寫一篇netty整合websocket,晚安!