天天看點

mybatis攔截器實作資料脫敏&攔截器使用

  今天遇到個需求需要對現有的資料進行脫敏處理。于是簡單研究了下。

  其實攔截器對脫敏處理主要處理兩種資料,一種是bean類型,一種是map類型。

  普通的javabean利用注解+反射來處理,map的資料自己維護需要脫敏的key以及規則。bean類型是用mybatis以及mybatis-plus自動生成的SQL映射的;map類型是手寫的傳回map類型的SQL和mybatis-plus的傳回map類型的資料。

1.主要代碼如下:

1.注解

package cn.xm.exam.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {
    
    DesensitionType type();

    String[] attach() default "";
}      

2.定義枚舉脫敏規則

package cn.xm.exam.mybatis;

public enum DesensitionType {
    PHONE("phone", "11位手機号", "^(\\d{3})\\d{4}(\\d{4})$", "$1****$2"),
    // ID_CARD("idCard", "16或者18身份證号", "^(\\d{4})\\d{8,10}(\\w{4})$",
    // "$1****$2"),
    ID_CARD("idCard", "16或者18身份證号", "^(\\d{4})\\d{11,13}(\\w{1})$", "$1****$2"), BANK_CARD("bankCardNo", "銀行卡号",
            "^(\\d{4})\\d*(\\d{4})$", "$1****$2"), ADDRESS("addrss", "位址", "(?<=.{3}).*(?=.{3})", "*"), REAL_NAME(
                    "realName", "真實姓名", "(?<=.{1}).*(?=.{1})", "*"), EMAIL("email", "電子郵箱", "(\\w+)\\w{5}@(\\w+)",
                            "$1***@$2"), CUSTOM("custom", "自定義正則處理", ""), TRUNCATE("truncate", "字元串截取處理", "");

    private String type;

    private String describe;

    private String[] regular;

    DesensitionType(String type, String describe, String... regular) {
        this.type = type;
        this.describe = describe;
        this.regular = regular;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    public String[] getRegular() {
        return regular;
    }

    public void setRegular(String[] regular) {
        this.regular = regular;
    }

}      

3.增加mybatis攔截器

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings({ "rawtypes", "unchecked" })
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), })
public class DesensitizationInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(DesensitizationInterceptor.class);

    private boolean desensitization = false;// 脫敏

    private static final Map<String, DesensitionType> desensitionMap = new LinkedHashMap<>();
    static {
        initDensensitionMap();
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();

        // 如果需要對結果脫敏,則執行
        if (desensitization) {
            // 先對Map進行處理
            if (result != null && result instanceof Map) {
                return this.desensitizationMap(result);
            }

            // 處理集合
            if (result instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) result;
                return this.desensitization(list);
            }

            // 處理單個bean
            return this.desensitization(result);
        }

        return result;
    }

    private static void initDensensitionMap() {
        desensitionMap.put("idCode", DesensitionType.ID_CARD);
        desensitionMap.put("idCard", DesensitionType.ID_CARD);
        desensitionMap.put("userIDCard", DesensitionType.ID_CARD);
        desensitionMap.put("userIdCard", DesensitionType.ID_CARD);

        desensitionMap.put("username", DesensitionType.REAL_NAME);
        desensitionMap.put("address", DesensitionType.ADDRESS);
    }

    /*
     * 對map脫敏
     */
    private Object desensitizationMap(Object result) {
        Map mapResult = (Map) result;
        if (MapUtils.isEmpty(mapResult)) {
            return mapResult;
        }

        Set<String> keySet = mapResult.keySet();
        for (String key : keySet) {
            if (desensitionMap.containsKey(key)) {
                DesensitionType desensitionType = desensitionMap.get(key);
                String replacedVal = getReplacedVal(desensitionType, MapUtils.getString(mapResult, key), null);
                mapResult.put(key, replacedVal);
            }
        }
        return result;
    }

    private List desensitization(List list) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }

        Class cls = null;
        for (Object o : list) {
            // 脫敏map,改變引用位址(根據靜态配置脫敏)
            if (o != null && o instanceof Map) {
                o = desensitizationMap(o);
                continue;
            }

            // 脫敏bean(根據注解脫敏)
            if (cls == null) {
                cls = o.getClass();
            }
            o = desensitization(o);
        }
        return list;
    }

    @Override
    public Object plugin(Object target) {
        // TODO Spring bean 方式配置時,如果沒有配置屬性就不會執行下面的 setProperties
        // 方法,就不會初始化,是以考慮在這個方法中做一次判斷和初始化
        return Plugin.wrap(target, this);
    }

    /**
     * 用于在Mybatis配置檔案中指定一些屬性的,注冊目前攔截器的時候可以設定一些屬性
     */
    @Override
    public void setProperties(Properties properties) {
    }

    private Object desensitization(Object obj) {
        if (obj == null) {
            return obj;
        }
        Class cls = obj.getClass();
        Field[] objFields = cls.getDeclaredFields();
        if (ArrayUtils.isEmpty(objFields)) {
            return obj;
        }

        for (Field field : objFields) {
            if ("serialVersionUID".equals(field.getName())) {
                continue;
            }

            Desensitization desensitization = null;
            if (String.class != field.getType()
                    || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
                continue;
            }

            try {
                field.setAccessible(true);
                String value = field.get(obj) != null ? field.get(obj).toString() : null;
                if (StringUtils.isBlank(value)) {
                    continue;
                }

                value = getReplacedVal(desensitization.type(), value, desensitization.attach());
                field.set(obj, value);
            } catch (Exception ignore) {
                ignore.printStackTrace();
            }
        }

        return obj;
    }

    private String getReplacedVal(DesensitionType type, String value, String[] attachs) {
        List<String> regular = null;
        switch (type) {
        case CUSTOM:
            regular = Arrays.asList(attachs);
            break;
        case TRUNCATE:
            regular = truncateRender(attachs);
            break;
        default:
            regular = Arrays.asList(type.getRegular());
        }

        if (regular != null && regular.size() > 1) {
            String match = regular.get(0);
            String result = regular.get(1);
            if (null != match && result != null && match.length() > 0) {
                value = ((String) value).replaceAll(match, result);
                return value;
            }
        }

        return "";
    }

    private List<String> truncateRender(String[] attachs) {
        List<String> regular = new ArrayList<>();
        if (null != attachs && attachs.length > 1) {
            String rule = attachs[0];
            String size = attachs[1];
            String template, result;
            if ("0".equals(rule)) {
                template = "^(\\S{%s})(\\S+)$";
                result = "$1";
            } else if ("1".equals(rule)) {
                template = "^(\\S+)(\\S{%s})$";
                result = "$2";
            } else {
                return regular;
            }
            try {
                if (Integer.parseInt(size) > 0) {
                    regular.add(0, String.format(template, size));
                    regular.add(1, result);
                }
            } catch (Exception e) {
                logger.warn("ValueDesensitizeFilter truncateRender size {} exception", size);
            }
        }
        return regular;
    }

    public boolean isDesensitization() {
        return desensitization;
    }

    public void setDesensitization(boolean desensitization) {
        this.desensitization = desensitization;
    }

}      

解釋: 

(1)Interceptor接口有三個方法,如下:

package org.apache.ibatis.plugin;
import java.util.Properties;

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}      

intercept方法中編寫我們自己的處理邏輯。類似于AOP。

(2)@Intercepts注解:

package org.apache.ibatis.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Clinton Begin
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}      

 Intercepts注解需要一個Signature(攔截點)參數數組。通過Signature來指定攔截哪個對象裡面的哪個方法。

(3)Signature注解指定需要攔截那個類對象的哪個方法

package org.apache.ibatis.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}      

class:指定定義攔截的類 Executor、ParameterHandler、StatementHandler、ResultSetHandler當中的一個。

method:指定攔截的方法,方法名字即可

args:指定攔截的方法對應的參數,JAVA裡面方法可能重載,不指定參數,不能确定調用那個方法。

4.mybatis的sqlSessionFactory中注冊攔截器

<!--2. 配置 Mybatis的會話工廠 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 資料源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置Mybatis的核心 配置檔案所在位置 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
        <!-- 注意其他配置 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!--使用下面的方式配置參數,一行配置一個 -->
                        <value>
                             helperDialect=mysql
                             reasonable=true
                        </value>
                    </property>
                </bean>
                <bean class="cn.xm.exam.mybatis.DesensitizationInterceptor">
                    <property name="desensitization" value="true"></property>
                </bean>
            </array>
        </property>
    </bean>      

5.Javabean中增加注解,如果是查詢傳回的map,會根據desensitionMap的規則進行脫敏

public class EmployeeIn {
    private String employeeid;

    /**
     * 員工編号
     */
    private String employeenumber;

    private String name;

    /**
     * 身份證号
     */
    @Desensitization(type = DesensitionType.ID_CARD)
    private String idcode;

    ...
}      

至此就可以實作一些基本的資料脫敏,前台檢視傳回的資訊如下:

mybatis攔截器實作資料脫敏&amp;攔截器使用

2.原理簡單介紹

  Mybatis攔截器設計的初衷就是為了供使用者在某些時候可以實作自己的邏輯而不必去動Mybatis固有的邏輯。通過Mybatis攔截器我們可以攔截某些方法的調用,我們可以選擇在這些被攔截的方法執行前後加上某些邏輯,也可以在執行這些被攔截的方法時執行自己的邏輯而不再執行被攔截的方法。

  Mybatis裡面的核心對象還是比較多的,如下:

mybatis攔截器實作資料脫敏&amp;攔截器使用

   Mybatis攔截器并不是每個對象裡面的方法都可以被攔截的。Mybatis攔截器隻能攔截Executor、ParameterHandler、StatementHandler、ResultSetHandler四個對象裡面的方法。

1.Executor接口如下:

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor;
import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Clinton Begin
 */
public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();
  
  void setExecutorWrapper(Executor executor);

}      

   Mybatis中所有的Mapper語句的執行都是通過Executor進行的。Executor是Mybatis的核心接口。從其定義的接口方法我們可以看出,對應的增删改語句是通過Executor接口的update方法進行的,查詢是通過query方法進行的。

2.ParameterHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * A parameter handler sets the parameters of the {@code PreparedStatement}
 *
 * @author Clinton Begin
 */
public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}      

  ParameterHandler用來設定參數規則,當StatementHandler使用prepare()方法後,接下來就是使用它來設定參數。是以如果有對參數做自定義邏輯處理的時候,可以通過攔截ParameterHandler來實作。

3.StatementHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.ibatis.executor.statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;

/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}      

  StatementHandler負責處理Mybatis與JDBC之間Statement的互動。

4.ResultSetHandler

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package org.apache.commons.dbutils;
import java.sql.ResultSet;
import java.sql.SQLException;

public interface ResultSetHandler<T> {

    T handle(ResultSet rs) throws SQLException;

}      

  ResultSetHandler用于對查詢到的結果做處理。是以如果你有需求需要對傳回結果做特殊處理的情況下可以去攔截ResultSetHandler的處理。

3.常見的攔截器

PageInterceptor 分頁插件

脫敏插件

4.攔截器實作資料庫查詢完加密,編輯關聯查詢解密

  今天遇到個需求是查詢之後需要對身份證以及住址資訊,并且在關聯查詢的時候需要使用原字元串,也就是需要解密。并且在編輯完儲存的時候需要還原成原字元串。

1. 自定義注解Crypt

package cn.xm.exam.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 加密解密注解
 * 
 * @author Administrator
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {

}      

2. 加密工具類

package cn.xm.exam.mybatis;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import cn.xm.exam.utils.SHAUtils;

public class CryptUtils {

    /**
     * 加密後字元串-原字元串
     */
    private static final Map<String, String> cryptedValues = new HashMap<>();

    public static String crypt(String originValue) {
        String encode = SHAUtils.sha1Hex(originValue + "mysalt");
        if (!cryptedValues.containsKey(encode)) {
            cryptedValues.put(encode, originValue);
        }

        return encode;
    }

    public static boolean hasCryptedVal(String value) {
        return cryptedValues.containsKey(value);
    }

    public static String decrypt(String value) {
        if (StringUtils.isBlank(value) || !hasCryptedVal(value)) {
            return value;
        }

        String decodeVal = cryptedValues.get(value);
        if (StringUtils.isBlank(decodeVal)) {
            return decodeVal;
        }

        return StringUtils.substringBeforeLast(decodeVal, "mysalt");
    }

}      

SHA摘要算法如下:

package cn.xm.exam.utils;


import org.apache.commons.codec.digest.DigestUtils;

/**
 * 摘要算法:SHA算法Secure Hash Algorithm(安全hash算法) 安全雜湊演算法(hash函數 将原始資訊壓縮
 * 傳回散列值)可以是SHA-1,SHA1是目前最安全 的摘要算法 摘要的長度為 20位元組
 * 
 * 其他的SHA 包括 SHA-256(32位元組)
 * 
 * 20byte = 160 bit,換成16進制字元串就是40位字元串
 * 
 * @author Administrator
 *
 */
public class SHAUtils {

    /**
     * 
     * @param sourceCode
     * @return 40位的16進制字元串
     */
    public static String sha1Hex(String sourceCode) {
        return DigestUtils.sha1Hex(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return length為20的位元組數組,如果轉為字元串需要new String(Hex.encodeHex(return))
     */
    public static byte[] sha1(String sourceCode) {
        // length為20的位元組數組
        return DigestUtils.sha1(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return 40位的16進制字元串
     */
    public static String sha256Hex(String sourceCode) {
        return DigestUtils.sha256Hex(sourceCode);
    }

    /**
     * 
     * @param sourceCode
     * @return length為20的位元組數組,如果轉為字元串需要new String(Hex.encodeHex(return))
     */
    public static byte[] sha256(String sourceCode) {
        // length為20的位元組數組
        return DigestUtils.sha256(sourceCode);
    }

    public static void main(String[] args) {
        System.out.println(sha1Hex("qlq"));
    }

}      

  SHA應該是不可逆的,我是将加密後的字元串作為key、原字元串作為value存入了map中來實作可逆。

3. 加密攔截器以及解密攔截器

  加密攔截器,攔截 ResultSetHandler 的Statement 方法,對查出的資料進行加密。

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 加密。出庫後加密(加密後傳到界面)
 * 
 * @author Administrator
 *
 */
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class EncryptInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(EncryptInterceptor.class);

    private static final List<String> cryptedKeys = new LinkedList<>();
    static {
        cryptedKeys.add("fullname");
        cryptedKeys.add("idCode");
        cryptedKeys.add("idCard");
        cryptedKeys.add("userIDCard");
        cryptedKeys.add("userIdCard");
        cryptedKeys.add("username");
        cryptedKeys.add("address");
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        if (resultObject == null) {
            return null;
        }

        // 基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (CollectionUtils.isNotEmpty(resultList)) {
                for (Object result : resultList) {
                    doEncrypt(result);
                }
            }
        } else {
            doEncrypt(resultObject);
        }

        return resultObject;
    }

    private void doEncrypt(Object result) {
        if (result instanceof Map) {
            Map resultMap = (Map) result;
            doEncryptMap(resultMap);
            return;
        }

        doEncryptPlainBean(result);
    }

    /**
     * 加密普通bean,用反射擷取字段進行加密
     * 
     * @param result
     */
    private void doEncryptPlainBean(Object result) {
        List<Field> allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
        for (Field field : allFieldsList) {
            field.setAccessible(true);
            try {
                Class<?> type = field.getType();
                if (!type.equals(String.class)) {
                    continue;
                }
                String value = (String) field.get(result);
                if (StringUtils.isBlank(value)) {
                    continue;
                }

                value = encrypt(value);
                field.set(result, value);
            } catch (Exception e) {
                log.error("doEncryptPlainBean error", e);
            }
        }
    }

    private String encrypt(String value) {
        return CryptUtils.crypt(value);
    }

    /**
     * 加密map
     * 
     * @param resultMap
     */
    private void doEncryptMap(Map resultMap) {
        if (MapUtils.isEmpty(resultMap)) {
            return;
        }

        Set keySet = resultMap.keySet();
        for (Object key : keySet) {
            String keyStr = (String) key;
            if (cryptedKeys.contains(keyStr)) {
                resultMap.put(key, encrypt(String.valueOf(resultMap.get(key))));
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

}      

解密攔截器:對入參進行解密。先判斷類型,隻對string、list<String>、map中的string、bean中的string屬性進行解密

package cn.xm.exam.mybatis;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解密攔截器。(查詢或者進行修改等操作時對參數進行解密)
 * 
 * @author Administrator
 *
 */
@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) })
public class DecryptInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(DecryptInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // @Signature 指定了 type= parameterHandler 後,這裡的 invocation.getTarget()
        // 便是parameterHandler
        // 若指定ResultSetHandler ,這裡則能強轉為ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 擷取參數對像,即 mapper 中 paramsType 的執行個體
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        // 取出執行個體(參數執行個體)
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            int count = 0;
            Class<?> parameterObjectClass = parameterObject.getClass();
            // 集合
            if (parameterObject instanceof List) {
                ArrayList resultList = (ArrayList) parameterObject;
                if (CollectionUtils.isNotEmpty(resultList)) {
                    for (Object result : resultList) {
                        result = doDecrypt(result);
                    }
                }
            }

            // 普通的bean
            parameterObject = doDecrypt(parameterObject);
        }
        // 重新指派引用
        parameterField.set(parameterHandler, parameterObject);

        Object proceed = invocation.proceed();
        return proceed;
    }

    private Object doDecrypt(Object result) {
        if (result == null) {
            return result;
        }

        Class<? extends Object> clazz = result.getClass();
        // String 類型
        if (clazz != null && clazz.equals(String.class)) {
            return decryptStr(result.toString());
        }

        if (result instanceof Map) {
            return decryptMap((Map) result);
        }

        // 普通bean
        return decryptPlainBean(result);
    }

    private Object decryptPlainBean(Object result) {
        List<Field> allFieldsList = FieldUtils.getFieldsListWithAnnotation(result.getClass(), Crypt.class);
        for (Field field : allFieldsList) {
            field.setAccessible(true);
            try {
                Class<?> type = field.getType();
                if (!type.equals(String.class)) {
                    continue;
                }

                Object object = field.get(result);
                field.set(result, decryptStr((String) object));
            } catch (Exception e) {
                log.error("doEncryptPlainBean error", e);
            }
        }

        return result;
    }

    private Object decryptMap(Map result) {
        if (result == null || MapUtils.isEmpty(result)) {
            return result;
        }

        Set keySet = result.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            Object object = result.get(key);
            if (object == null) {
                continue;
            }

            if (object instanceof String) {
                result.put(key, decryptStr((String) object));
            }
        }

        return result;
    }

    private Object decryptStr(String string) {
        return CryptUtils.decrypt(string);
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

}      
<!--2. 配置 Mybatis的會話工廠 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 資料源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置Mybatis的核心 配置檔案所在位置 -->
        <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
        <!-- 注意其他配置 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!--使用下面的方式配置參數,一行配置一個 -->
                        <value>
                             helperDialect=mysql
                             reasonable=true
                        </value>
                    </property>
                </bean>
                <bean class="cn.xm.exam.mybatis.EncryptInterceptor">
                </bean>
                <bean class="cn.xm.exam.mybatis.DecryptInterceptor">
                </bean>
            </array>
        </property>
    </bean>      
@Crypt
    private String useridcard;      

【當你用心寫完每一篇部落格之後,你會發現它比你用代碼實作功能更有成就感!】