MyBatis(八):MyBatis插件机制详解
MyBatis插件插件机制简介
MyBatis插件其实就是为使用者提供的自行拓展拦截器,主要是为了可以更好的满足业务需要。
在MyBatis中提供了四大核心组件对数据库进行处理,分别是Executor、Statement Handler、ParameterHandler及ResultSetHandler,同时也支持对这四大组件进行自定义扩展拦截,用来增强核心对象的功能。其本质上是使用底层的动态代理来实现的,即程序运行时执行的都是代理后的对象。
MyBatis允许拦截的方法如下:
执行器Executor (update、query、commit、rollback等方法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
参数处理器ParameterHandler (getParameterObject、setParameters方法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
拦截器参数简介
拦截器注解
@Intercepts(//可配置多个@Signature,均使用此类进行拦截增强
{
@Signature(//指定需要拦截的类、方法及方法参数
type = Executor.class,//需要拦截接口
method = "update",//需要拦截方法名称
args = {MappedStatement.class, Object.class}//拦截方法的请求参数
)
}
)
实现Interceptor接口,并实现方法
public Object intercept(Invocation invocation)//每次拦截到都会执行此方法,方法内写增强逻辑
invocation//代理对象,可以获取目标方法、请求参数、执行结果等
invocation.proceed() //执行目标方法
public Object plugin(Object target)
Plugin.wrap(target,this)//包装目标对象,为目标对象创建代理对象,将当前生成的代理对象放入拦截器链中
public void setProperties(Properties properties)//获取配置文件中的插件参数,插件初始化时调用一次
自定义插件
新建MyExecuter类,实现Interceptor接口
package com.rangers.plugin; import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*; import java.lang.reflect.Method;
import java.util.Properties; /**
* @Author Rangers
* @Description 自定义Executor update方法
* @Date 2021-03-11
**/
@Intercepts({
@Signature(type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MyExecuter implements Interceptor { // 接收插件的配置参数
private Properties properties = new Properties(); // 增强逻辑写在此方法中
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 打印插件的配置参数
System.out.println("插件的配置参数:"+properties.toString());
// 获取目标方法Method对象
Method method = invocation.getMethod();
// 获取目标方法的请求参数 与args列表一一对应
String reqParam = invocation.getArgs()[1].toString();
System.out.println("方法名称:"+method.getName()+" 请求参数:"+ reqParam);
// 执行目标方法
Object result = invocation.proceed();
System.out.println("方法名称:"+method.getName()+" 执行结果:"+result);
return result;
} /**
* @Author Rangers
* @Description
**/
@Override
public Object plugin(Object target) {
//System.out.println("需要包装的目标对象:"+target+" 目标对象类型"+ target.getClass());
// 主要是将当前生成的代理对象放入拦截器链中,包装目标对象,为目标对象创建代理对象
return Plugin.wrap(target,this);
} /**
* @Author Rangers
* @Description 获取配置文件中的插件属性参数,插件初始化时调用一次
**/
@Override
public void setProperties(Properties properties) {
// 将配置参数进行接收
this.properties = properties;
}
}
主配置文件添加标签
<plugins>
<!--指定拦截器类-->
<plugin interceptor="com.rangers.plugin.MyExecuter">
<!--配置拦截器 属性参数-->
<property name="param1" value="value1"/>
<property name="param2" value="value2"/>
<property name="param3" value="value3"/>
</plugin>
</plugins>
插件原理
a、在Executor、StatementHandler、ParameterHandler及ResultSetHandler四大对象创建时,并不是直接返回的,而是中间多了一步interceptorChain.pluginAll()(均在Configuration类中进行创建)。
- Executor—interceptorChain.pluginAll(executor);
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;
}
- StatementHandler—interceptorChain.pluginAll(statementHandler);
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;
}
- ParameterHandler—interceptorChain.pluginAll(parameterHandler);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
- ResultSetHandler—interceptorChain.pluginAll(resultSetHandler)
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;
}
b、interceptorChain.pluginAll()调用的就是实现了Interceptor接口的plugin()方法,plugin()方法又通过Plugin.wrap(target,this)为目标对象创建一个Plugin的代理对象,添加到拦截链interceptorChain中。具体代码如下:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 调用实现Interceptor接口的plugin方法
target = interceptor.plugin(target);
}
return target;
}
@Override
public Object plugin(Object target) {
// 调用Plugin的wrap()方法,创建代理对象
return Plugin.wrap(target,this);
}
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;
}
c、Plugin实现了 InvocationHandler接口,因此它的invoke方法会拦截所有的方法调用。invoke()方法会 对所拦截的方法进行检测,以决定是否执行插件逻辑。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 根据组件对象Class从signatureMap中获取到需要拦截的方法set集合
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);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 获取到所有拦截器的类
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());
}
// 获取到@Signature注解数组
Signature[] sigs = interceptsAnnotation.value();
// signatureMap存放所有拦截到方法,key为四大组件的Class,value为组件对应的方法set集合
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
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;
}
PageHelper插件
PageHelper是MyBaits框架使用最广泛的第三方物理分页插件,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
使用步骤:
添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.2</version>
</dependency>
配置插件
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
使用分页
@org.junit.Test
public void testPagehealper() {
PageHelper.startPage(1, 2);
List<User> users = userDao.findAll();
if (users != null && users.size() > 0) {
for (User user : users) {
System.out.println(user.toString());
}
PageInfo<User> pageInfo = new PageInfo<>(users);
System.out.println("总条数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("当前页:" + pageInfo.getPageNum());
System.out.println("每页显万长度:" + pageInfo.getPageSize());
System.out.println("是否第一页:" + pageInfo.isIsFirstPage());
System.out.println("是否最后一页:" + pageInfo.isIsLastPage());
}
}
MyBatis(八):MyBatis插件机制详解的更多相关文章
- Maven使用教程三:maven的生命周期及插件机制详解
前言 今天这个算是学习Maven的一个收尾文章,里面内容不局限于标题中提到的,后面还加上了公司实际使用的根据profile配置项目环境以及公司现在用的archetype 模板等例子. 后面还会总结一个 ...
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)
文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...
- 《深入理解mybatis原理2》 Mybatis初始化机制详解
<深入理解mybatis原理> Mybatis初始化机制详解 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程 ...
- Quartz学习——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成详解(转)
通过前面的学习,你可能大致了解了Quartz,本篇博文为你打开学习SSMM+Quartz的旅程!欢迎上车,开始美好的旅程! 本篇是在SSM框架基础上进行的. 参考文章: 1.Quartz学习——Qua ...
- 《深入理解mybatis原理6》 MyBatis的一级缓存实现详解 及使用注意事项
<深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 0.写在前面 MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓 ...
- [转载]Mybatis Generator最完整配置详解
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...
- maven常用插件配置详解
常用插件配置详解Java代码 <!-- 全局属性配置 --> <properties> <project.build.name>tools</proje ...
- ssion机制详解
ssion机制详解 ref:http://justsee.iteye.com/blog/1570652 虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚sess ...
- hibernate缓存机制详解
hiberante面试题—hibernate缓存机制详解 这是面试中经常问到的一个问题,可以按照我的思路回答,准你回答得很完美.首先说下Hibernate缓存的作用(即为什么要用缓存机制),然后再 ...
随机推荐
- c#记两个变量进行值交换
今天腊月二十九啦,无心上班,专注划水.然后就在那里翻帖子消磨时光. 看到了这样一个问题,有人提问为什么 a=b+(b=a)*0 ??? 第一眼看上去,我也有点蒙,仔细推敲了一下,嗯~的确是交换了 ...
- LINUX - openssl学习
参考链接: https://www.cnblogs.com/AloneSword/p/4656492.html
- kubernetes实战-交付dubbo服务到k8s集群(三)安装配置maven和java运行时环境的底包镜像
maven 官方地址: 官方地址 下载maven,shdd7-200 # cd /opt/src # wget https://archive.apache.org/dist/maven/maven- ...
- ArcGIS Mobile 自定义图层在绘制面时节点未绘制完全的问题
ArcGIS Mobile 自定义图层在绘制面时节点未绘制完全,如下图: 面的绘制代码如下: public void Draw(Display dis, Pen p1, Pen p2,Pen p3 , ...
- Bootstrap微章
给链接.导航等元素嵌套 span class="badge" 元素,可以很醒目的展示新的或未读的信息条目. <a href="#">Inbox &l ...
- IDEA 安装常用操作一
关于IDEA的下载,破解自行百度 一.安装完成的常用设置 SDK选择.编译版本的选择,单项目选择,全局选择 maven配置,单项目,全局配置 二.IDEA如何安装lombok https://www. ...
- Mac 开机时为什么突然响一下,duang
Mac 开机时为什么突然响一下,duang duang 一下 https://zh.wikipedia.org/wiki/Duang refs xgqfrms 2012-2020 www.cnblog ...
- GraphQL All In One
GraphQL All In One refs https://github.com/hasura/learn-graphql xgqfrms 2012-2020 www.cnblogs.com 发布 ...
- front-end & web & best code editor
front-end & web & best code editor 2019 VS Code https://designrevision.com/best-code-editor/ ...
- clientHeight & offsetHeight & scrollHeight
clientHeight & offsetHeight & scrollHeight scrollWidth/scrollHeight,offsetWidth/offsetHeight ...