Mybatis的插件,是采用责任链机制,通过JDK动态代理来实现的。默认情况下,Mybatis允许使用插件来拦截四个对象:
这个我们可以从Mybatis的源码中看到,例如下面创建Executor的时候,就是返回了一个代理Executor对象:
开始进入源码环节。
下面是InterceptorChain 类的源码:可以看到内部有一个拦截器链,调用pluginAll方法时,会遍历所有的拦截器的plugin方法
public class InterceptorChain {
private final List interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
查看 Interceptor源码:它是一个接口,其plugin方法又调用了Plugin.wrap方法,继续跟入。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
Plugin源码:进到这里,思路就豁然开朗了,wrap方法里就是通过JDK动态代理创建了一个代理对象。下面我们逐行看下,都经过了哪些步骤。
/**
* Copyright 2009-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map, Set> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
// 得到拦截器上的注解的 classType、 method、parameter参数的Map
Map, Set> signatureMap = getSignatureMap(interceptor);
// 获取被代理的Class类型
Class> type = target.getClass();
// 找到signatureMap中包含的 type 的所有父类接口
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 创建一个代理对象
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 methods = signatureMap.get(method.getDeclaringClass());
// 当前执行的方法是被代理的话,则去执行对应的拦截器的intercept方法
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
// 非代理方法,直接回调执行,返回结果即可
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map, Set> getSignatureMap(Interceptor interceptor) {
// 获取拦截器上的 Intercepts 注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
// 得到注解的 classType、 method、parameter参数,放入Map中,返回
Signature[] sigs = interceptsAnnotation.value();
Map, Set> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
Set> interfaces = new HashSet<>();
while (type != null) {
for (Class> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[0]);
}
}
1.
Map, Set > signatureMap = getSignatureMap(interceptor);
getSignatureMap方法,获取拦截器上的注解内容。得到拦截器上的@Intercepts注解,解析 Intercepts注解上的参数,放入HashMap集合中,格式: {{key=type, value=[method(args...}, ...}
,这些参数用于表明该拦截器用来拦截哪些类的哪些方法。说的有点抽象,上代码,如下:
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
解析的就是Intercepts注解内部Signature里面的type、method、args这些参数。(上面注解的意思是,拦截Executor类的update(MappedStatement mappedStatement, Object obj)方法!)
private static Map, Set> getSignatureMap(Interceptor interceptor) {
// 获取拦截器上的 Intercepts 注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
// 得到注解的 type、 method、parameter参数,放入Map中,返回
Signature[] sigs = interceptsAnnotation.value();
Map, Set> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
2.
// 获取被代理的Class类型 Class> type = target.getClass();
这一行肉眼可见,就是用于得到被代理的Class对象。
3.
// 找到type 的所有被拦截的接口 Class>[] interfaces = getAllInterfaces(type, signatureMap);
这里就是遍历出type的接口中,被拦截的接口:
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
Set> interfaces = new HashSet<>();
while (type != null) {
for (Class> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[0]);
}
4.创建代理对象
if (interfaces.length > 0) { // 创建一个代理对象 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target;
所以当执行target 中的方法时,就会执行Plugin的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try {// 代理对象执行方法时,进入这里,先判断该方法是否是被拦截器拦截的方法 Setmethods = 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); } }
至此源码分析完毕!接下来演示实现一个分页插件。
分页实体类:
public class PageInfo implements Serializable {
private int totalNumber; // 当前表中总条目数量
private int currentPage; // 当前页数
private int totalPage; // 总页数
private int pageSize=3; // 每页显示条数
private int startIndex=1; // 检索的起始位置
private int totalSelect; // 检索的总数目
public int getTotalNumber() {
return totalNumber;
}
public void setTotalNumber(int totalNumber) {
this.totalNumber = totalNumber;
// 计算
this.count();
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getTotalSelect() {
return totalSelect;
}
public void setTotalSelect(int totalSelect) {
this.totalSelect = totalSelect;
}
public void count(){
int totalPageTmp = this.totalNumber / this.pageSize;
// 总条数不能被页面大小整除时,总页数 + 1
int plus = (this.totalNumber % this.pageSize) == 0? 0:1;
totalPageTmp += plus;
if (totalPageTmp<=0){
totalPageTmp = 1;
}
this.totalPage = totalPageTmp; // 计算得到总页数
if (this.totalPage
插件类:
@Intercepts(@Signature(type=StatementHandler.class, method="prepare", args={Connection.class, Integer.class}))
public class SimbaPagePlugin implements Interceptor {
private Log log = LogFactory.getLog(this.getClass());
private String type;
// 插件的核心业务
@Override
public Object intercept(Invocation invocation) throws Throwable {
/**
* 1. 拿到原始的SQL语句: select * from user;
* 2. 修改原始SQL,加上分页: select * from user limit 0,3;
* 3. 执行jdbc方法查询总数: select count(1) from (select * from user) a;
*/
// 从 invocation 中获取 StatementHandler 对象
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
// 拿到原始 SQL
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
log.info("$$$$$$$$$$$$$$$$$$$$$$$$ 原始sql: " + sql);
// 拿到分页参数
Object paramObj = boundSql.getParameterObject();
// 将 statementHandler 对象转成 metaObject
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// 从metaObject对象中取得 MappedStatement 对象
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
// 获取 mapper 接口中的方法名称, 如: selectUserByPage
String mapperMethodName = mappedStatement.getId();
log.info("$$$$$$$$$$$$$$$$$$$$$$$$ mapperMethodName: " + mapperMethodName);
// 匹配到以 "ByPage"结尾的方法
if (mapperMethodName.matches(".*ByPage$")){
Map params = (Map) paramObj;
PageInfo pageInfo = (PageInfo)params.get("page");
// 查询总条数
String countSql = "select count(0) from ("+ sql + ") a";
// 执行 jdbc 操作
Connection conn = (Connection)invocation.getArgs()[0];
PreparedStatement ps = conn.prepareStatement(countSql);
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
parameterHandler.setParameters(ps);
ResultSet rs = ps.executeQuery();
if (rs.next()){
pageInfo.setTotalNumber(rs.getInt(1));
}
rs.close();
ps.close();
// 改造原始 sql,加上 limit 分页
String pageSql = this.generatePageSql(sql, pageInfo);
log.info("$$$$$$$$$$$$$$$$$$$$$$$$ 分页sql: " + pageSql);
// 更新执行流程中的 sql
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
// 把执行流程交给mybatis
return invocation.proceed();
}
private String generatePageSql(String sql, PageInfo pageInfo) {
StringBuilder sb = new StringBuilder();
sb.append(sql);
if ("mysql".equalsIgnoreCase(type)){
sb.append(" limit " + pageInfo.getStartIndex() + " , " + pageInfo.getTotalSelect());
}else if ("oracle".equalsIgnoreCase(type)){
//TODO
}
return sb.toString();
}
// 把自定义的插件加入到mybatis中去执行
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 设置属性
@Override
public void setProperties(Properties properties) {
type = properties.getProperty("type"); // 可以根据这里的得到的属性,做适配:mysql、Oracle、Mongodb
log.info("$$$$$$$$$$$$$$$$$$$$$$$$ type: " + type);
}
}
public class Test {
public static void testJdbc() throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 打开会话
try (SqlSession session = sqlSessionFactory.openSession()){
UserMapper userMapper = session.getMapper(UserMapper.class);
PageInfo pageInfo = new PageInfo();
pageInfo.setPageSize(4);
Map param = new HashMap<>();
param.put("page", pageInfo);
List pageData = userMapper.selectByPage(param);
for (User u: pageData){
System.out.println("pageData: " + u);
}
}
}
public static void main(String[] args) throws Exception {
testJdbc();
}
}