# MyBatis 插件原理
大多数框架都支持插件,用户可通过编写插件来自行扩展功能,Mybatis 也不例外。
# 集成分页插件 PageHelper
在 Mybatis 中最出名的就是 PageHelper 分页插件,下面我们先来使用一下这个分页插件。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
1
2
3
4
5
2
3
4
5
配置分页插件配置项
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
1
2
3
4
5
2
3
4
5
service 接口代码中
PageInfo selectUsersByName(int pageIndex, int pageSize);
1
service 实现类代码中
@Override
public PageInfo selectUsersByName(int pageIndex, int pageSize) {
PageHelper.startPage(pageIndex, pageSize);
List<User> users = userMapper.selectUsersByName(null);
return new PageInfo(users);
}
1
2
3
4
5
6
2
3
4
5
6
Mapper 代码代码
<select id="selectUsersByName" resultMap="User">
select * from m_user
<where>
<if test="userName != null and userName != ''">
`name` = #{userName}
</if>
</where>
</select>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
List<User> selectUsersByName(@Param("userName") String userName);
1
TIP
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
# Interceptor 中三个方法的作用
- intercept():执行拦截内容的地方,比如:在调用某类方法前后做一些自己的处理,简单就是打印日志。
- plugin():决定是否触发 intercept()方法。
- setProperties():给自定义的拦截器传递我们配置的属性参数
# plugin 方法
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
1
2
3
2
3
默认实现方法,里面调用了 Plugin.wrap()方法。
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 创建JDK动态代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 判断是否是需要拦截的方法(很重要)
if (methods != null && methods.contains(method)) {
// 回调intercept()方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//...省略其他不相关代码
}
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
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
Map<Class<?>, Set> signatureMap:缓存需拦截对象的反射结果,避免多次反射,即 target 的反射结果。- 所以,我们不要动不动就说反射性能很差,那是因为你没有像 Mybatis 一样去缓存一个对象的反射结果。
- 判断是否是需要拦截的方法,这句注释很重要,一旦忽略了,都不知道 Mybatis 是怎么判断是否执行拦截内容的,要记住。
# Plugin.wrap(target, this)是干什么的
使用 JDK 的动态代理,给 target 对象创建一个 delegate 代理对象,以此来实现方法拦截和增强功能,它会回调 intercept()方法。
# 为什么要写注解?注解都是什么含义
在我们自定义的插件上有一堆注解,别害怕。
Mybatis 规定插件必须编写 Annotation 注解,是必须,而不是可选。
@Intercepts({@Signature( type= Executor.class, method = "update",
args = {MappedStatement.class,Object.class})}
)
public class TianPlugin implements Interceptor {}
1
2
3
4
2
3
4
- @Intercepts 注解:装载一个@Signature 列表,一个@Signature 其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截多个方法,自然就是一个@Signature 列表。
- type= Executor.class, method = "update",args = {MappedStatement.class,Object.class}
解释:要拦截 Executor 接口内的 query()方法,参数类型为 args 列表。