廢話不多少,終於弄懂了mybatis plugin

一、前言

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
來源:掘金

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章