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的用法] ...
随机推荐
- 隐藏在 Nebula Graph 背后的星辰大海
本文首发于 Nebula Graph Community 公众号 作者介绍 大家好,我是 Anyzm,graph-ocean(GitHub:https://github.com/nebula-cont ...
- 利用微软官方API实现Office文档的在线预览功能
随着互联网时代的飞速发展,越来越多的工作开始依赖于云端服务,我们的办公方式也逐渐发生了翻天覆地的变化.在这种背景下,急需一种无需本地安装Office软件,就能快速查看和共享Word.PowerPoin ...
- Netty笔记(5) - 编码解码机制 和 Protobuf技术
介绍: 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码 codec(编解码器) 的组成部分有两个:decoder(解码器)和 encode ...
- 那些.NET中的连接池
前言 在.NET中,连接池被广泛用于管理和优化不同类型资源的连接.连接池可以减少建立和关闭连接所需的时间和资源消耗,从而提高了应用程序的性能和响应能力. HttpClient中的连接池 System. ...
- C++中OpenCV、Armadillo矩阵数据格式的转换方式
本文介绍在C++语言中,矩阵库Armadillo的mat.vec格式数据与计算机视觉库OpenCV的Mat格式数据相互转换的方法. 在C++语言的矩阵库Armadillo与计算机视觉库Open ...
- java中webSocket发送图片文件数据非常慢
一.问题由来 目前在开发的这个小程序中有一个功能需要和Unity客户端进行互动操作,互动的大致流程为在微信小程序中点击一个操作,发送一个HTTP请求, Java后台收到这个请求后,会给Unity客户端 ...
- Module not specified-使用IDEA出现问题
一.问题由来 使用IDE导入一个项目时,准备启动这个项目,然后突然报错,错误信息如标题中所示Module not specified.这个项目之前都还好好的 怎么突然就运行不了了呢?让我感到很是疑惑, ...
- IIS web.config 跨域设置 不包含 options的设置 thinkphp tp3 跨域
web.config <?xml version="1.0" encoding="UTF-8"?> <configuration> &l ...
- 阿里云Python UDP Server和client基础教程
壹: socket通信是常用的一种通信方式,熟练掌握,快速的入戏,是一个程序员必备的素质. 贰: 注意:udp和tcp的套接字: 服务端代码: #!/usr/bin/env python3 # -*- ...
- 干货分享 | UE游戏鼠标双击判定
UE虚幻引擎对于游戏开发者来说都不陌生,市面上有47%主机游戏使用虚幻引擎开发游戏.作为是一款游戏的核心动力,它的功能十分完善,囊括了场景制作.灯光渲染.动作镜头.粒子特效.材质蓝图等.本文介绍了虚幻 ...