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的用法] ...
随机推荐
- 【Azure 环境】ARM部署模板大于4MB的解决方案及Linked Template遇见存储账号防火墙无法访问
问题一:在ADF Pipeline部署ARM Template报错"Deployment failed -- the request content size exceeds the max ...
- 【Azure 应用服务】在安全漏洞扫描中发现有泄露服务器IIS版本的情况,如何实现屏蔽服务版本号信息呢?
问题描述 当对Azure App Service应用进行安全扫描时,发现了HTTP/S请求的响应头中会包含服务端IIS的版本信息,这是一个低风险因素. 如: Server: Microsoft-IIS ...
- sed 资源
sed教程 菜鸟教程正则 MDN正则 正则测试工具 文本替换 s sed有多种分割符,比如你要替换路径字符串时,使用反斜杠很难看,则可以用 : 或者 _ 或者 | 这三个符号都可作为分隔符. & ...
- nginx部署SSL证书后,使用域名访问报错-net::ERR_SSL_PROTOCOL_ERROR
一.问题由来 最近在做一个小程序的后台,自己去微信官网上查看了相关的规定,小程序正式发布时,要求比较严格,必须是使用https+域名访问,自己在 阿里云购买了一个域名,可是没有备案.SSL证书去阿里云 ...
- SpringMVC简介 & 原理
特点 1.轻量级,简单易学 2.高效,基于请求响应的MVC框架 3.与Spring兼容性好,与之无缝接合(就是它的一部分) 4.约定优于配置(maven) 5.功能强大:支持RESTful 数据验证 ...
- iview 部分表单验证
引用:https://github.com/ElemeFE/element/issues/3686 zxmantou commented on 25 Feb 2019 @Murraya-panicul ...
- java生产者消费者模式代码示例
package test; import java.util.LinkedList; public class Test { public static void main(String[] args ...
- Hibernate之Criteria
1,Criteria Hibernate 设计了 CriteriaSpecification 作为 Criteria 的父接口,下面提供了 Criteria和DetachedCriteria . ...
- window10-yarn-使用vite创建vue3项目失败-文件夹或目录不正确
前置条件 window10 本地已经安装nodejs yarn已经通过npm全局安装(npm install -g yarn) 问题 yarn脚手架方式搭建vue3项目失败(command faile ...
- HeaderedContentControl实现左右对称
在我们使用TextBlock却想给前面添加固定字段的时候,发现TextBlock没有Header属性, 这个时候我们可以用到HeaderedContentControl 然而,默认情况下Headere ...