首頁>科技>

一、前言

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,晚安!

10
最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 網際網路養豬鼻祖,丁磊的黑豬肉賣得咋樣了?賣不掉只能送人?