拦截器介绍
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
执行:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)请求参数处理:
ParameterHandler (getParameterObject, setParameters)返回结果集处理:
ResultSetHandler (handleResultSets, handleOutputParameters)SQL 语句构建:
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。具体可以参考 官方文档。
拦截器的使用
我们要实现数据加密,进入数据库的字段不能是真实的数据,但是返回来的数据要真实可用,所以我们需要针对 Parameter 和 ResultSet 两种类型处理,同时为了更灵活的使用,我们需要自定义注解。
自定义注解
我们可以在我们要处理的实体和实体中的字段加上需要的注解
1
2
3
4
5
6
7
8/**
* 需要加解密的类注解
*/
public EncryptDecryptClass {}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 加密字段注解
*/
public EncryptDecryptField {
/**
* 字段是否使用, 分割符号拼接而成(SQL 查询使用 GROUP_CONCAT)
* @return boolean
*/
boolean split() default false;
}自定义参数处理拦截器
参考官网,通过 @Intercepts 和 @Signature 的联合使用,指定 ParameterHandler.class 类型,同时通过 @Component 注解注入到容器中,即可在设置参数的时候进行拦截,根据 Field 的各种类型自定义加密解密算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ParammeterInterceptor implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
if (!(invocation.getTarget() instanceof ParameterHandler)) {
return invocation.proceed();
}
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
Object parameterObject = parameterHandler.getParameterObject();
if (Objects.isNull(parameterObject)) {
return invocation.proceed();
}
Class<?> parameterObjectClass = parameterObject.getClass();
EncryptDecryptClass encryptDecryptClass = parameterObjectClass.getAnnotation(EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)){
EncryptDecryptUtils.encrypt(EncryptDecryptUtils.getFields(parameterObjectClass), parameterObject);
}
return invocation.proceed();
}
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}结果集拦截器
与参数拦截器基本一样, 只不过类型指定为 ResultSetHandler.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ResultInterceptor implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (Objects.isNull(result)){
return null;
}
if (result instanceof List) {
EncryptDecryptUtils.fieldListDecrypt(result);
}else if(EncryptDecryptUtils.notToDecrypt(result)) {
EncryptDecryptUtils.decrypt(EncryptDecryptUtils.getFields(result.getClass()), result);
}
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}加密解密工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146public class EncryptDecryptUtils {
/**
* 多 field 加密方法
*
* @param declaredFields 字段
* @param parameterObject 类
* @return T
* @throws IllegalAccessException IllegalAccessException
*/
public static <T> void encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException {
for (Field field : declaredFields) {
encrypt(field, parameterObject);
}
}
/**
* 多个 field 解密方法
*
*
* @param declaredFields 解密字段
* @param result 结果
* @throws IllegalAccessException IllegalAccessException
*/
public static void decrypt(Field[] declaredFields, Object result) throws IllegalAccessException {
for (Field field : declaredFields) {
decrypt(field, result);
}
}
/**
* 单个 field 加密方法
*
* @param field 字段
* @param parameterObject 类
* @return T
* @throws IllegalAccessException IllegalAccessException
*/
private static <T> void encrypt(Field field, T parameterObject) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(parameterObject);
if (object instanceof String) {
field.set(parameterObject, AESUtil.encrypt((String)object));
}
}
/**
* 单个 field 解密方法
*
* @param field 字段
* @param result 结果
* @throws IllegalAccessException IllegalAccessException
*/
private static void decrypt(Field field, Object result) throws IllegalAccessException {
fieldObjectDecrypt(field, result);
field.setAccessible(true);
Object object = field.get(result);
if (Objects.isNull(object)) {
return;
}
if (object instanceof String) {
EncryptDecryptField decryptField = field.getAnnotation(EncryptDecryptField.class);
if (!decryptField.split()) {
field.set(result, AESUtil.decrypt((String)object));
return;
}
String[] strings = ((String) object).split(",");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, length = strings.length - 1; i < length; i++) {
stringBuilder.append(AESUtil.decrypt(strings[i])).append(",");
}
stringBuilder.append(AESUtil.decrypt(strings[strings.length - 1]));
field.set(result, stringBuilder.toString());
}
}
/**
* 是否解密
* @param object 对象
* @return 是否解密
*/
public static boolean notToDecrypt(Object object){
Class<?> objectClass = object.getClass();
EncryptDecryptClass encryptDecryptClass = objectClass.getAnnotation(EncryptDecryptClass.class);
return Objects.isNull(encryptDecryptClass);
}
/**
* 获取有@EncryptDecryptField 注解的字段
* @param objectClass 类
* @return 字段
*/
public static Field[] getFields(Class<?> objectClass) {
Field[] fields = Arrays.stream(objectClass.getDeclaredFields())
.filter(field -> Objects.nonNull(field.getAnnotation(EncryptDecryptField.class)))
.toArray(Field[]::new);
Class<?> superClass = objectClass.getSuperclass();
if (Object.class == superClass) {
return fields;
}
return ArrayUtils.addAll(fields, Arrays.stream(superClass.getDeclaredFields())
.filter(field -> Objects.nonNull(field.getAnnotation(EncryptDecryptField.class)))
.toArray(Field[]::new));
}
/**
* 字段对象的解密
* @param field 字段
* @param result 数据
* @throws IllegalAccessException IllegalAccessException
*/
private static void fieldObjectDecrypt(Field field, Object result) throws IllegalAccessException {
field.setAccessible(true);
Object object = field.get(result);
if (object instanceof List) {
fieldListDecrypt(object);
return;
}
Class<?> objectClass = field.getType();
EncryptDecryptClass encryptDecryptClass = objectClass.getAnnotation(EncryptDecryptClass.class);
if (Objects.nonNull(encryptDecryptClass)) {
decrypt(getFields(objectClass), field.get(result));
}
}
/**
* 字段集合的解密
* @param object 集合数据
* @throws IllegalAccessException IllegalAccessException
*/
public static void fieldListDecrypt(Object object) throws IllegalAccessException {
ArrayList resultList = (ArrayList) object;
if (resultList.isEmpty() || EncryptDecryptUtils.notToDecrypt(resultList.get(0))){
return;
}
Field[] declaredFields = EncryptDecryptUtils.getFields(resultList.get(0).getClass());
if (ArrayUtils.isEmpty(declaredFields)) {
return;
}
for (Object o : resultList) {
decrypt(declaredFields, o);
}
}
private EncryptDecryptUtils() {}
}
数据库同样有 AES 加解密函数:
AES_ENCRYPT(str,key_str)
以及AES_DECRYPT(crypt_str,key_str)
。可以执行SELECT @@block_encryption_mode;
查看加密模式, 修改为 256 位:SET @@block_encryption_mode = 'aes-256-ecb';
原文链接
Fraser Yu:Mybatis 拦截器之数据加密解密