目录
Mybaits插件简介
为自己的框架提供插件和其他形式的扩展点,主要就是供开发者能自行拓展。比如可以做分页、分表、监控等功能。
由于插件和业务无关,业务也无法感知插件的存在。
因此可以无感植入插件,无形中增强功能。
而Mybaits作为一个应用广泛的优秀的ORM开源框架,具有强大的灵活性,这个灵活性主要体现在四大组件上:
Executor、StatementHandler、ParameterHandler、ResultSetHandler
这四大组件提供了简单易用的插件扩展方式。
Mybaits对持久层的操作就是借助四大核心对象。
Mybaits支持用插件对四大核心对象进行拦截,从而实现功能的增强效果。
增强功能的本质其实就是使用动态代理实现的。换句话说,Mybaits中的四大对象都是代理对象。
这四个组件的关系图如下:
MyBatis四大组件所允许拦截的⽅法
执⾏器 Executor (update、query、commit、rollback等⽅法);
SQL语法构建器 StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
参数处理器 ParameterHandler (getParameterObject、setParameters⽅法);
结果集处理器 ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);
Mybaits插件开发案例
自己开发一个Mybaits插件还是比较简单的。
第一步:先创建一个MyPlugin类,需要实现Mybatis自己写的接口Interceptor,具体可以看看下面的代码和注释
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
// 这个注解就是解释了当前我们写的这个拦截器主要是拦截mybaits四大组件中的哪个组件,
// 目前我们是在SQL语法构建时执行我们的增强方法 intercept()
// 也可以理解为是在mybaits执行的什么阶段去进行功能增强
public class MyPlugin implements Interceptor {
/**
* 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
* 看上面的注释可以得知我们配置的拦截方法是
* StatementHandler类中的prepare方法
* 一旦该方法执行,就会被 这个方法执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行了增强方法");
return invocation.proceed();
}
/**
* 主要为了把当前的拦截器生成代理存到拦截器链中
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("把当前的拦截器生成代理存到拦截器链中");
Object wrap = Plugin.wrap(target, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置文件的参数是:" + properties);
}
}
第二步:将我们自定义的插件,配置在sqlMapConfig.xml文件中,注意加入的位置顺序。
<!-- 自定义插件 -->
<plugins>
<plugin interceptor="cn.dyc.components.demo.mybaits.quick.start.plugin.MyPlugin">
<property name="name" value="xiaoxue"/>
</plugin>
</plugins>
第三步:当我们执行之前测试的方法调用Mybaits的查询方法时,可以看到已经成功了
并且通过观察我们可以发现重写的三个方法的执行顺序为:
setProperties -> plugin -> intercept
自定义插件源码分析
我们都知道在自定义插件的时候,需要先将插件加入到拦截器里:
Object wrap = Plugin.wrap(target, this);
那么我们就从Plugin.class入手
这个类实现了InvocationHandler接口,也就是他会执行 invoker方法
观察这个方法可以发现,在获取我们注解上配置的类、方法、参数后生成的Method对象,如果当前执行的这个方法正好是我们配置需要拦截的方法,那么就会调用interceptor.intercept方法,也就是之前我们重写的那个方法。
这个插件还是比较简单的,本身没有特别复杂的逻辑,存储依赖于jdk中的 动态代理 + 反射 这两种技术来实现插件。
@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);
}
}
pagehelper源码分
析
其实自定义的分析过了之后,基本原理也就搞明白了,剩下的就看怎么玩成花了。
比如说用的很火的分页插件 pagehelper 怎么用呢?分析下!
pagehelper案例展示
①pom中加入依赖
<!-- 分页助手 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
②把插件类配置到sqlMapConfig.xml中,PageHelper这个类怎么找到的,源码包中没几个类,唯一实现 interceptor 的就是这个类
<!-- 自定义插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
③执行测试类就成功了
@Test
public void pageTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
PageHelper.startPage(1, 1);
List<User> list = userDao.findAll();
PageInfo<User> userPageInfo = new PageInfo<>(list);
System.out.println("总条数:" + userPageInfo.getTotal());
System.out.println("总页数:" + userPageInfo.getPages());
System.out.println("当前页:" + userPageInfo.getPageNum());
System.out.println("每页显示的条数:" + userPageInfo.getPageSize());
sqlSession.close();
}
pagehelper源码分析
核心类就是实现了 interceptor 的
PageHelper
这个方法里面有一个工具类 SqlUtil,真正去执行SQL的时候就是靠这个工具类去实现分页的
在setProperties阶段,就已经把这个工具类实例化出来了,并且添加了他的方言,这也就是为什么我们在集成这个插件的时候在配置文件中需要加上那么一个<property name=”dialect” value=”mysql”/>的值了
当执行intercept()方法时,就是执行在PageHelper上用各种静态方法赋值的参数交由sqlUtil这个类去执行,比如
PageHelper.startPage(1, 1);
设置他的当前页数和每页数据
那么为什么执行 PageHelper.startPage 必须挨着 要执行的sql语句这个问题,大家应该也已经明白了吧
SqlUtil会用静态方法把这个Page对象封装到本地线程中的page参数中,供后面执行sql语句是进行sql拼装
Page page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
SqlUtil.setLocalPage(page);
return page;