Mybatis攔截器改寫請求參數(shù)和結(jié)果

系列

開篇

  • 近期因?yàn)轫椖康男枨蟪榭昭芯苛讼耺ybatis的攔截器碉考,因?yàn)橹坝衜ybatis的源碼的基礎(chǔ)塌计,所以熟悉起來稍微順利一些。
  • 關(guān)于Mybatis的原理可以參考MyBatis攔截器原理介紹的文章侯谁,算是打個基礎(chǔ)吧锌仅。


請求參數(shù)攔截改寫

/**
 * 通過注解來表明,我們需要對那個字段進(jìn)行加密
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ParamAnnotation {
    String[] srcKey() default {};

    String[] destKey() default {};
}
  • 定義注解用來在方法上墙贱,指定加密前字段和加密后字段。


@Repository
public interface UserManageMapper {

    @ParamAnnotation(srcKey = {"phone"}, destKey = {"phone"})
    Integer addOneUser(UserInfoVo userInfoVo);
}
  • 在方法上增加注解,指定加密后前后的字段轴合,后續(xù)解析到方法層面存在注解我們就就去修改參數(shù)晓殊。
  • 之所以在方法層面使用注解是為了減少影響面,降低已有業(yè)務(wù)的風(fēng)險魁衙。


import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.util.Properties;

@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class ParamInterceptor implements Interceptor {


    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 獲取攔截器攔截的設(shè)置參數(shù)對象DefaultParameterHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

        // 通過mybatis的反射來獲取對應(yīng)的值
        MetaObject metaResultSetHandler = MetaObject.forObject(parameterHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        MappedStatement mappedStatement = (MappedStatement) metaResultSetHandler.getValue("mappedStatement");
        Object parameterObject = metaResultSetHandler.getValue("parameterObject");

        // id字段對應(yīng)執(zhí)行的SQL的方法的全路徑报腔,包含類名和方法名
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1);

        // 動態(tài)加載類并獲取類中的方法
        final Method[] methods = Class.forName(className).getMethods();

        // 遍歷類的所有方法并找到此次調(diào)用的方法
        for (Method method : methods) {
            if (method.getName().equalsIgnoreCase(methodName)
                    && method.isAnnotationPresent(ParamAnnotation.class)) {

                // 獲取方法上的注解以及注解對應(yīng)的參數(shù)
                ParamAnnotation paramAnnotation = method.getAnnotation(ParamAnnotation.class);
                String srcKey = paramAnnotation.srcKey()[0];
                String destKey = paramAnnotation.destKey()[0];

                // 反射獲取參數(shù)對象
                MetaObject param = MetaObject.forObject(parameterObject, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
                Object srcValue = param.getValue(srcKey);

                // 動態(tài)加工指定參數(shù)
                String destValue = String.valueOf(srcValue) + "fix";

                // 將修改后的動態(tài)參數(shù)添加到請求參數(shù)當(dāng)中
                param.setValue(destKey, destValue);

                break;
            }
        }

        // 回寫parameterObject對象
        metaResultSetHandler.setValue("parameterObject", parameterObject);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
  • 針對方法參數(shù)的攔截器定義在ParameterHandler的setParameters方法。
  • MetaObject.forObject是mybatis提供的反射方法剖淀,簡便了反射獲取和修改字段纯蛾。
  • ParameterHandler的攔截器的Invocation的target為ParameterHandler對象。
  • 核心的步驟在代碼中通過注釋標(biāo)明了纵隔,可以自行閱讀了解翻诉。


請求結(jié)果攔截改寫

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface EncryptResultFieldAnnotation {

    /**
     * 加密策略 -- 和加密字段炮姨,一一對應(yīng)
     */
    Class<? extends IEncryptResultFieldStrategy>[] encryptStrategy() default {};

    /**
     * 加密字段對應(yīng)的key
     */
    String[] fieldKey() default {};

}
  • 定義注解用來在方法上,指定加密前字段和加密方法米丘。


@Repository
public interface UserManageMapper {

    @EncryptResultFieldAnnotation(fieldKey = "password", encryptStrategy = PasswordEncryptStrategy.class)
    UserInfoVo getOneUser();
}
  • 在方法層面使用注解剑令,減少影響面,降低風(fēng)險拄查。


import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.*;

/**
 * 通過攔截器對返回結(jié)果中的某個字段進(jìn)行加密處理
 */
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}
        )
})
public class EncryptResultFieldInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 獲取到返回結(jié)果
        ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
        MetaObject metaResultSetHandler = MetaObject.forObject(resultSetHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        MappedStatement mappedStatement = (MappedStatement) metaResultSetHandler.getValue("mappedStatement");
        EncryptResultFieldAnnotation annotation = getEncryptResultFieldAnnotation(mappedStatement);
        Object returnValue = invocation.proceed();
        if (annotation != null && returnValue != null) {
            String[] fieldKeyList = annotation.fieldKey();
            Class<? extends IEncryptResultFieldStrategy>[] strategyClassList = annotation.encryptStrategy();
            if (strategyClassList.length != 0 && fieldKeyList.length == strategyClassList.length) {
                Map<String, Class<? extends IEncryptResultFieldStrategy>> strategyMap = null;
                for (int index = 0; index < fieldKeyList.length; index++) {
                    if (strategyMap == null) {
                        strategyMap = new HashMap<>();
                    }
                    strategyMap.put(fieldKeyList[index], strategyClassList[index]);
                }
                // 對結(jié)果進(jìn)行處理
                try {
                    if (returnValue instanceof ArrayList<?>) {
                        List<?> list = (ArrayList<?>) returnValue;
                        for (int index = 0; index < list.size(); index++) {
                            Object returnItem = list.get(index);
                            if (returnItem instanceof String) {
                                List<String> stringList = (List<String>) list;
                                IEncryptResultFieldStrategy encryptStrategy = strategyMap.get(fieldKeyList[0]).newInstance();
                                stringList.set(index, encryptStrategy.encrypt((String) returnItem));
                            } else {
                                MetaObject metaReturnItem = MetaObject.forObject(returnItem, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
                                for (Map.Entry<String, Class<? extends IEncryptResultFieldStrategy>> entry : strategyMap.entrySet()) {
                                    String fieldKey = entry.getKey();
                                    IEncryptResultFieldStrategy fieldEncryptStrategy = entry.getValue().newInstance();
                                    Object fieldValue = metaReturnItem.getValue(fieldKey);
                                    if (fieldValue instanceof String) {
                                        metaReturnItem.setValue(fieldKey, fieldEncryptStrategy.encrypt((String) fieldValue));
                                    }
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    // ignore
                }

            }
        }
        return returnValue;

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 獲取方法上的EncryptResultFieldAnnotation注解
     *
     * @param mappedStatement MappedStatement
     * @return EncryptResultFieldAnnotation注解
     */
    private EncryptResultFieldAnnotation getEncryptResultFieldAnnotation(MappedStatement mappedStatement) {
        EncryptResultFieldAnnotation encryptResultFieldAnnotation = null;
        try {
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            String methodName = id.substring(id.lastIndexOf(".") + 1);
            final Method[] method = Class.forName(className).getMethods();
            for (Method me : method) {
                if (me.getName().equals(methodName) && me.isAnnotationPresent(EncryptResultFieldAnnotation.class)) {
                    encryptResultFieldAnnotation = me.getAnnotation(EncryptResultFieldAnnotation.class);
                    break;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return encryptResultFieldAnnotation;
    }
}
  • 針對請求結(jié)果的攔截器定義在ResultSetHandler的handleResultSets方法吁津。
  • 通過MetaObject.forObject在返回結(jié)果中獲取指定字段并經(jīng)過處理設(shè)置到返回結(jié)果當(dāng)中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堕扶,一起剝皮案震驚了整個濱河市碍脏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稍算,老刑警劉巖典尾,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糊探,居然都是意外死亡钾埂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門科平,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褥紫,“玉大人,你說我怎么就攤上這事瞪慧∷杩迹” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵弃酌,是天一觀的道長氨菇。 經(jīng)常有香客問我,道長妓湘,這世上最難降的妖魔是什么查蓉? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮多柑,結(jié)果婚禮上奶是,老公的妹妹穿的比我還像新娘。我一直安慰自己竣灌,他們只是感情好聂沙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著初嘹,像睡著了一般及汉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屯烦,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天坷随,我揣著相機(jī)與錄音房铭,去河邊找鬼。 笑死温眉,一個胖子當(dāng)著我的面吹牛缸匪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播类溢,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼凌蔬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闯冷?” 一聲冷哼從身側(cè)響起砂心,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛇耀,沒想到半個月后辩诞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纺涤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年译暂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撩炊。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秧秉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衰抑,到底是詐尸還是另有隱情,我是刑警寧澤荧嵌,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布呛踊,位于F島的核電站,受9級特大地震影響啦撮,放射性物質(zhì)發(fā)生泄漏谭网。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一赃春、第九天 我趴在偏房一處隱蔽的房頂上張望愉择。 院中可真熱鬧,春花似錦织中、人聲如沸锥涕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽层坠。三九已至,卻和暖如春刁笙,著一層夾襖步出監(jiān)牢的瞬間破花,已是汗流浹背谦趣。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留座每,地道東北人前鹅。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像峭梳,于是被迫代替她去往敵國和親舰绘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容