【mybatis】mybatis 拦截器工作原理源码解析
mybatis 拦截器工作原理(JDK动态代理)
1. mybatis 拦截器案例
场景:分页查询,类似成熟产品:pagehelper, 这里只做简单原理演示
1.0 mybatis全局配置 SqlMapConfig.xml
1.1 StudentMapper.xml 映射器配置文件
1.2 StudentMapper: 映射器
public interface StudentMapper {
public List queryAll();
}
1.3 StudentService: 服务类
public static List queryByPage(Integer pageNow, Integer pageSize) {
PageHelper.startPage(pageNow, pageSize);
try{
return sqlSession.getMapper(StudentMapper.class).queryAll();
}finally{
sqlSession.close();
}
}
1.4 分页拦截器
1.4.1 分页参数 PageParam
package com.zhiwei.advanced.plugin;
import lombok.Data;
@Data
public class PageParam {
private Integer pageNow;
private Integer pageSize;
private Boolean startPageMark;
}
1.4.2 分页辅助类
package com.zhiwei.advanced.plugin;
public final class PageHelper {
// 通过ThreadLocal保存分页参数,相同线程实现分页参数共享
private static ThreadLocal threadPageParam = new ThreadLocal();
public static void startPage(Integer pageNow, Integer pageSize){
PageParam pageParam = new PageParam();
pageParam.setPageNow((pageNow == null ? 1 : pageNow));
pageParam.setPageSize((pageSize == null ? 10 : pageSize));
pageParam.setStartPageMark(true);
threadPageParam.set(pageParam);
}
public static PageParam getPageParam(){
try{
PageParam pageParam = threadPageParam.get();
}finally{
threadPageParam.remove(); // 清理本地线程缓存,防止内存泄漏
}
return pageParam;
}
}
1.4.3 分页SQL处理工具类
package com.zhiwei.advanced.plugin;
import java.util.HashMap;
import java.util.Map;
public final class PageSqlUtil {
private static final Map pageFormatMap = new HashMap<>();
// 这里拿MYSQL做案例,PG分页参数用法不一样,需进一步细分处理
static{
pageFormatMap.put("MYSQL", " limit %d , %d");
}
public static String getPageSql(String databaseName, PageParam pageParam){
if (databaseName == null || !pageFormatMap.containsKey(databaseName.toUpperCase())){
throw new RuntimeException("插件暂不支持此数据库"+ databaseName +"类型");
}
return String.format(pageFormatMap.get(databaseName.toUpperCase()), (pageParam.getPageNow() - 1) * pageParam.getPageSize(), pageParam.getPageSize());
}
}
1.4.4 拦截器
package com.zhiwei.advanced.plugin;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@Intercepts(
// 拦截StatementHandler的prepare方法,也就是生成PrepareStatement阶段,为后面PS的参数设置做准备
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
)
@Slf4j
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截对象:这里拦截StatementHandler,默认是RoutingStatementHandler装饰类,实际委托处理类:PreparedStatementHandler
if (invocation.getTarget() instanceof StatementHandler) {
// 当前工作线程未设置分页参数,则直接返回
PageParam pageParam = PageHelper.getPageParam();
if (null == pageParam || !pageParam.getStartPageMark()) {
return invocation.proceed();
}
//mybatis获取对象成员的方法:本质反射代理机制
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, new DefaultObjectFactory(),
new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
// 获取委托对象delegate(PreparedStatementHandler)的boundSql对象的sql成员:对应原生sql: 案例为:select * from student
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
// 重新设置经过处理后的sql数据:select * from student limit (pageParam.getPageNow() - 1) * pageParam.getPageSize(), pageParam.getPageSize()
metaStatementHandler.setValue("delegate.boundSql.sql", buildSql(getDatabaseInfo((Connection) invocation.getArgs()[0]), originalSql, pageParam));
}
return invocation.proceed();
}
private String getDatabaseInfo(Connection connection){
String databaseName = null;
try {
databaseName = connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
e.printStackTrace();
}
log.info("连接数据库名称:{}", databaseName);
return databaseName;
}
private String buildSql(String databaseName, String originalSql, PageParam pageParam){
if (pageParam == null){
return originalSql;
}
return originalSql + PageSqlUtil.getPageSql(databaseName, pageParam);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
1.4.5 测试
@Test
public void queryByPage() {
List students = StudentService.queryByPage(2, 5);
log.info("students:{}", students);
}
2. Interceptor 工作原理
拦截四大对象:
- Executor(执行器,负责数据库业务执行整个流程): org.apache.ibatis.session.Configuration#newExecutor
- ParameterHandler(参数处理器:为Statement设置请求参数的值):org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
- StatementHandler(语句处理器:统一调度Statement生成过程,构成完整版的PrepareStatement):org.apache.ibatis.session.Configuration#newStatementHandler
- ResultSetHandler(处理数据库响应之后的记录,映射成自定义的实体类,方便处理):org.apache.ibatis.session.Configuration#newResultSetHandler: resultSetHandler
2.1 Interceptor 注册
全局配置文件SqlMapConfig.xml 配置Plugins标签,案例为:MyInterceptor
标签解析:org.apache.ibatis.builder.xml.XMLConfigBuilder.pluginElement
Interceptor维护:Configuration.interceptorChain
2.2 Interceptor 工作流程(案例:StatementHandler)
2.2.1 构造 StatementHandler
接口:org.apache.ibatis.session.Configuration.newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建StatementHandler装饰器:StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// mybatis 对拦截的对象进行处理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
2.2.2 拦截器链工作流程
接口:org.apache.ibatis.plugin.InterceptorChain.pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 拦截器拦截目标对象,本质返回JDK动态代理对象(StatementHandler的代理对象),如果存在多个拦截器则层层代理
target = interceptor.plugin(target);
}
return target;
}
2.2.3 拦截器动态代理逻辑
案例接口:com.zhiwei.advanced.plugin.MyInterceptor.plugin
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
代理对象生成逻辑
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
//signatureMap:保存拦截器拦截目标及方法
// getAllInterfaces: 判断目标对象是否是拦截器拦截的范围,根据目标对象类型确定
Class>[] interfaces = getAllInterfaces(type, signatureMap);
// 若目标对象在拦截器拦截范围则直接生成代理对象返回,否则直接返回原生对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 目标对象不在拦截器拦截范围,直接返回
// 例如:案例中拦截StatementHandler,若目标对象为ParameterHandler,则直接不处理原生返回
return target;
}
//判断目标对象是否在拦截器拦截范围
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>[interfaces.size()]);
}
2.2.4 拦截器生成代理对象处理逻辑
接口:org.apache.ibatis.plugin.Plugin
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
// 目标对象执行方法在拦截器拦截范围,则直接拦截交给拦截器处理,否则直接执行
// 案例:StatementHandler拦截Prepare方法,但是执行parameterize并不在拦截范围,则跳过拦截器直接处理
if (methods != null && methods.contains(method)) {
// 案例对应:com.zhiwei.advanced.plugin.MyInterceptor.intercept, Invocation包含拦截的目标对象,执行方法、参数, 注意这里目标对象是StatementHandler装饰类:RoutingStatementHandler
return interceptor.intercept(new Invocation(target, method, args));
}
// 目标方法不在拦截范围,直接执行,相当于生成的动态代理类不作任何处理
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
.......................................... mybatis Interceptor 工作原理分析完毕 ................................................
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341