mybatis plugin源码解析
概述
Plugin,意为插件,是mybatis为开发者提供的,对方法进行自定义编程的手段。其中用到了动态代理、反射方法,通过指定需要增强的对象与方法,进行程序编写。
核心类
主要涉及几个核心类:Interceptor、Plugin、Intercepts
该增强功能的大致执行顺序为:
项目启动时,查询实现了
Interceptor接口并且注册为Bean(或在xml文件中指定)的类,放入SqlSessionFactoryBean的Interceptor[]参数中,再由SqlSessionFactoryBean创建SqlSessionFactory的时候,将其放入Configuration参数中,留作后续调用// **注意interceptorsProvider,此为SpringBoot的configuration,会自动查询注册为Bean的Interceptor**
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
} @Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
.....
// **注意此处,将interceptors放入factory中**
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
.....
} public class SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
.......
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
} if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
....... return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
}
Configuration类在初始化
ParameterHandler、ResultSetHandler、StatementHandler、Executor四个类时,会对它们进行一次封装,封装内容即为用Interceptors注册插件功能,达到增强效果public class Configuration { public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
} public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
} public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
} public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
} }
Configuration执行的pluginAll方法,内部是通过遍历Interceptor数组的plugin方法实现的。该方法入参和出参都是Object类型,所以可以认为它能为所有类型对象都进行增强封装。Interceptor内部调用了Plugin的wrap方法,对Object对象进行了封装。public class InterceptorChain { private final List<Interceptor> 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<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
} } 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方法实现了InvocationHandler动态代理类,并且wrap方法本身便是创建动态代理类。故Plugin类的职责有两项:- 创建动态代理类,指定需要被代理(增强)的对象。此处为
Executor、Handler等。 - 指定被动态代理的对象,需要执行何种程序。重点关注
invoke方法。
public class Plugin implements InvocationHandler { private final Object target;
private final Interceptor interceptor;
private final 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) {
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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
} // 省略getSignatureMap,getAllInterfaces方法
...
}
- 创建动态代理类,指定需要被代理(增强)的对象。此处为
wrap方法执行时需要先通过interceptor获取signatureMap。
Signature是Intercepts注解中的value值注解,由于此value的返回值是数组,所以Signature会多个存在,最后解析出的结果便为signatureMap。Signature注解的作用为标注被动态代理的对象,具体的类型(class),具体的方法,方法具体的参数。只有特定类型和方法才会执行Interceptor方法。public class Plugin implements InvocationHandler { private final Object target;
private final Interceptor interceptor;
private final 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;
} private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
**// 通过此代码可发现,实现Interceptor的类必须添加Intercepts注解**
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
**// 通过Siganture的method与args,反射出Method对象,将其添加到map中
// 作用是在执行动态代理invoke方法时,判断当前方法是否需要被interceptor执行**
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;
} }
通过阅读源码可知,开发者需要自己实现
Interceptor,标记Intercepts注解,指定需要拦截的类、方法名,方法上的参数类型。并将Interceptor注册为Spring Bean。即可在interceptor方法中编写具体拦截代码。
实例
背景:在项目上为每一个需要插入至数据库中的实例对象,初始化id。
代码:
@Component
**// 拦截Executor类的update方法,该update方法会执行insert、update、delete操作**
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class MybatisUpdateInterceptor implements Interceptor {
// 雪花算法id生成器
@Autowired
private IdGenerator idGenerator;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
**// 判断是否为insert方法**
if (ms.getSqlCommandType() != SqlCommandType.INSERT) {
return invocation.proceed();
}
BaseEntity entity = (BaseEntity) invocation.getArgs()[1];
if (entity.getId() == null) {
entity.setId(idGenerator.generate());
}
return method.invoke(invocation.getTarget(), invocation.getArgs());
}
}
总结
项目开发者可灵活运用plugin,为数据库操作进行增强。日常开发中也可借鉴此流程,通过动态代理方式设计拦截/增强手段。
mybatis plugin源码解析的更多相关文章
- MyBatis详细源码解析(上篇)
前言 我会一步一步带你剖析MyBatis这个经典的半ORM框架的源码! 我是使用Spring Boot + MyBatis的方式进行测试,但并未进行整合,还是使用最原始的方式. 项目结构 导入依赖: ...
- MyBatis 3源码解析(一)
一.SqlSessionFactory 对象初始化 //加载全局配置文件 String resource = "mybatis-config.xml"; InputStream i ...
- Mybatis SqlNode源码解析
1.ForEachSqlNode mybatis的foreach标签可以将列表.数组中的元素拼接起来,中间可以指定分隔符separator <select id="getByUserI ...
- Mybatis SqlSessionTemplate 源码解析
As you may already know, to use MyBatis with Spring you need at least an SqlSessionFactory and at le ...
- MyBatis 3源码解析(四)
四.MyBatis 查询实现 Employee empById = mapper.getEmpById(1); 首先会调用MapperProxy的invoke方法 @Override public O ...
- MyBatis 3源码解析(三)
三.getMapper获取接口的代理对象 1.先调用DefaultSqlSession的getMapper方法.代码如下: @Override public <T> T getMapper ...
- MyBatis 3源码解析(二)
二.获取SqlSession对象 1.首先调用DefaultSqlSessionFactory 的 openSession 方法,代码如下: @Override public SqlSession o ...
- mybatis源码解析1--前言
在开始分析mybatis源码之前,需要定一个目标,也就是我们不是为了读源码而去读,一定是带着问题去读,在读的时候去寻找到答案,然后再读码的同时整理总结,学习一些高级的编码方式和技巧. 首先我们知道my ...
- Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...
- Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
随机推荐
- OPPO 自研大规模知识图谱及其在数智工程中的应用
导读:OPPO 知识图谱是 OPPO 数智工程系统小布助手团队主导.多团队协作建设的自研大规模通用知识图谱,目前已达到数亿实体和数十亿三元组的规模,主要落地在小布助手知识问答.电商搜索等场景. 本文主 ...
- GPS 方案总结
GPS 方案 搜集网络上关于GPS的方案. redis + mysql redis 用来做设备或用户实时定位的查询. mysql存储历史轨迹.存储时分两部分,一张表做实时查询用.一张表做备份用.如果需 ...
- OPPO 后端面试凉经(附详细参考答案)
这篇文章的问题来源于一个读者之前分享的 OPPO 后端凉经,我对比较典型的一些问题进行了分类并给出了详细的参考答案.希望能对正在参加面试的朋友们能够有点帮助! Java String 为什么是不可变的 ...
- 【Azure Power BI】Power BI获取SharePoint List列表后,如何展开List/Table中的字段,以及使用逗号拼接为一个字符串
问题描述 Power BI获取SharePoint List列表作为数据源.但是在数据源中,有Table属性值,有List属性值.如果直接展开,则会形成"笛卡尔"集的效果,变成N多 ...
- 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 ctrl+down 光标下移4行
为什么 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 之前滚动屏幕总是用鼠标,现在改为 ctrl + 上箭头或下箭头 实现起来稍微有些麻烦 实现 需要安装 macros 插件 这 ...
- 【图算法】图卷积的演变-从谱图卷积到GCN
基础 傅里叶变换 傅里叶级数是对周期为T的确定性信号做展开,而傅里叶变换将周期推广到无穷,能对具有任意长度的信号做展开. 傅里叶级数和傅里叶变换是什么关系? 如下为傅里叶变换公式: \[\hat{f} ...
- python3 Crypto模块实例解析
一 模块简介 1.简介 python的Crypto模块是安全hash函数(例如SHA256 和RIPEMD160)以及各种主流的加解密算法的((AES, DES, RSA, ElGamal等)的集合. ...
- python数组概念和实例解析
一 概念 如果我们需要一个只包含数字的列表,那么array.array比list更高效.数组支持所有跟可变序列有关的操作,包括.pop,.insert和.extend. 另外,数组还提供从文件读取和存 ...
- C++实现常用的文件操作
①输出到文本文件(txt) 1 #include<iostream> 2 #include<fstream> 3 using namespace std; 4 int main ...
- drf(请求和响应)
一 请求 源码分析 from rest_framework.request import Request class Request: def __init__(self, request, pars ...