Mybatis源码学习第十一课---插件开发原理
程序员文章站
2024-01-02 23:44:58
...
一.插件开发原理
插件是用来改变或者扩展mybatis的原有功能,mybatis的插件就是通过继承Interceptor拦截器实现的,在没有完全理解插件之前禁止使用插件对mybatis进行扩展,否则可能会导致严重问题。
mybatis中能使用插件进行拦截的接口和方法如下:
- Executor(update query flushStatement commit rollback getTransaction close isClose)
- StatementHandler(prepare parameterize batch update query)
- ParameterHandler (getParameterObject setParameters)
- ResultHandler(handlerResults handlerCursorResultSets handleOutputParameters)
插件开发的步骤:
- step1.实现Interceptor接口方法
- step2.确定拦截的签名
- step3.在配置文件中配置插件
- step4.运行测试用例
二.插件开发实例
定义一个阈值,当查询操作运行时间超过这个阈值,记录日志供运维人员定位慢查询。下面是在日志debug模式下,如果不是debug等级下 不需要取Statement代理对象
- step1.实现Interceptor接口方法
@Intercepts({
@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
private long threshold;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
Object ret = invocation.proceed();
long end=System.currentTimeMillis();
long runTime = end - begin;
if(runTime>=threshold){
Object[] args = invocation.getArgs();
Statement stat = (Statement) args[0];
MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
Statement statement = statementLogger.getPreparedStatement();
System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"+runTime+"毫秒,已经超过阈值!");
}
return ret;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.threshold = Long.valueOf(properties.getProperty("threshold"));
}
}
特别说明:PreparedStatementLogger实现了InvocationHandler接口,是代理对象中的h,也是让类具有日志打印功能的代理增强类。
PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
- step2.确定拦截的签名
拦截StatementHandler的query语句。不拦截Executor,是因为Executor在调用StatementHandler的query前后,还做了很多其他事情。
public interface StatementHandler {
//执行select语句
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
@Intercepts({
@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
- step3.在配置文件中配置插件
plugins放到配置文件mybatis-config.xml最后报异常了,最后放在environment前面。
<plugins>
<plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor">
<property name="threshold" value="10"/>
</plugin>
</plugins>
- step4.运行测试用例
@Test
// 测试自动映射以及下划线自动转化驼峰
public void quickStart() throws IOException {
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
// 4.执行查询语句并返回结果
TUser user = mapper.selectByPrimaryKey(1);
System.out.println(user.toString());
}
结果:
sql语句:“aaa@qq.com: select
id, user_name, real_name, sex, mobile, email, note,
position_id
from t_user
where id = 1”执行时间为:15毫秒,已经超过阈值!
TUser [id=1, userName=lison, realName=李小宇, sex=1, mobile=186995587422, aaa@qq.com, note=lison的备注, positionId=]
3.责任链模式
责任链模式把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作,和装饰器模式不同,每个节点都知道后继者是谁,适合为完成同一个请求需要多个处理类的场景。
- Handler:定义了一个处理请求的标准接口;
ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可以结束处理流程,也可以将请求转发给它的后继者;
client:发送者,发起请求的客户端。
责任链模式的优点:
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象,使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,运行动态地新增或删除责任。
- 增加新的请求处理类很方便。
4.mybatis插件模块源码分析
4.1 插件的初始化(XMLConfigBuilder.pluginElement)
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>节点
pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//遍历所有的插件配置
for (XNode child : parent.getChildren()) {
//获取插件的类名
String interceptor = child.getStringAttribute("interceptor");
//获取插件的配置
Properties properties = child.getChildrenAsProperties();
//实例化插件对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置插件属性
interceptorInstance.setProperties(properties);
//将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
configuration.addInterceptor(interceptorInstance);
}
}
}
public class Configuration {
/*插件集合*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
注意:责任链的顺序是以链表的顺序为准的,链表的顺序又是以<plugins>配置顺序为准的,因此,责任链中每一个处理节点不需要知道下一个处理节点是谁,达到解耦的目的。
<plugins>
<plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor">
<property name="threshold" value="10"/>
</plugin>
<!-- <plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="pageSizeZero" value="true" />
</plugin> -->
</plugins>
4.2 插件的加载(Configuration.new*方法,四大对象的创建)
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) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
调试可知加载的时机如下:
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
调用ThresholdInterceptor.plugin():
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//静态方法,用于帮助Interceptor生成动态代理
public static Object wrap(Object target, Interceptor interceptor) {
//解析Interceptor上@Intercepts注解得到的signature信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();//获取目标对象的类型
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口(拦截器可以拦截4大对象实现的接口)
if (interfaces.length > 0) {
//使用jdk的方式创建动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
4.3 插件的调用(Plugin.wrap、Plugin.invoke)
@Override
//查询的实现
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();//获取configuration对象
//创建StatementHandler对象,
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
query的时候,进入invoke调用:
@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)) {//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
return interceptor.intercept(new Invocation(target, method, args));
}
//如果当前方法不需要被拦截,则调用对象自身的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
调用是各个变量的值:
这里调用ThresholdInterceptor的intercept:
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
Object ret = invocation.proceed();
long end=System.currentTimeMillis();
其中proceed为:
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
5.Inteceptor调用原理——核心就是递归思想
interceptorChain.pluginAll 方法:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
前面我们配置拦截器的顺序是1,2,3。在这里也会按照 1,2,3 的顺序被层层代理,代理后的结构如下:
Interceptor3:{
Interceptor2: {
Interceptor1: {
target: Executor
}
}
}
整体的调用逻辑:
Interceptor3 前置处理
Interceptor2 前置处理
Object result = executor.query(6个参数方法);
Interceptor2 后续处理
Interceptor3 后续处理
return result;
参考
- 1)享学课堂Lison老师笔记
- 2)PageHelper的Interceptor原理说明