mybatis|深入理解MyBatis(五)—MyBatis的插件机制
深入理解MyBatis(五)—MyBatis的插件机制
MyBatis提供了插件机制用于自定义的拓展,其实质是实现了拦截器的功能;(0) 插件机制
个人主页:tuzhenyu’s page
原文地址:深入理解MyBatis(五)—MyBatis的插件机制
- MyBatis允许使用插件拦截具体的方法调用,具体的拦截点包含四类:
- Executor:update(),query(),flushStatement(),commit(),rollback(),getTransaction(),close(),isClosed()等方法;
- ParameterHandler:getParameterObject(),setParameters()等方法;
- ResultSetHandler:handleResultSets(),handleOutputParameters();
- StatementHandler:prepare(),parameterize(),batch(),update(),query()
- Executor:update(),query(),flushStatement(),commit(),rollback(),getTransaction(),close(),isClosed()等方法;
- 在初始化Configuration对象时会解析标签生成Interceptor实例并加入拦截器链InterceptorChain;插件生成的时机就是Executor,ParameterHandler,ResultSetHandler,StatementHandler这四个接口的实现类初始化的时候,会调用pluginAll()方法,根据InterceptorChain拦截链中的拦截器决定是否生成代理类;
- 拦截器执行的顺序是:Executor,ParameterHandler,ResultSetHandler,StatamentHandler;
- 创建插件类ExamplePlugin实现接口Interceptor,主要的插件逻辑在interceptor()方法中实现;
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
}finally {
long end = System.currentTimeMillis();
System.out.println("cost time:"+(end-start));
}
}@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}@Override
public void setProperties(Properties properties) {}
}
- 在config.xml配置文件中添加plugins配置,其中的节点是有顺序的:
(2) 插件机制的实现流程 1. 插件配置的初始化
- 在Configuration对象初始化的时候,会调用pluginElement()方法对标签进行解析;
- 获取标签对应的intercptor类名,通过反射的方式生成对应的拦截器类实例interceptor;
- 获取标签下的所有标签,解析成Properties对象,并将Properties设置到拦截器中;
- 将生成的拦截器实例interceptor放入configuration中;
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.addInterceptor(interceptorInstance);
}
}
}
- 调用addInterceptor()方法将Interceptor实例放入configuration中的属性InterceptorChain中;InterceptorChain是一个Interceptor的链表;
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {private final List 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 getInterceptors() {
return Collections.unmodifiableList(interceptors);
}}
2. 拦截器的执行流程
Executor的拦截器是在sqlSession生成阶段创建Executor实例时通过pluginAll()方法添加拦截器的
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;
}
ParameterHandler,ResultSetHandler,StatamentHandler拦截器是在SQL具体执行阶段通过pluginAll()方法添加拦截器的
- StatementHandler初始化时候调用pluginAll()方法添加拦截器
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;
}
- StatementHandler初始化时候会创建ParameterHandler和ResultSetHandler实例
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
- ParameterHandler初始化时候调用pluginAll()方法添加拦截器
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初始化时候调用pluginAll()方法添加拦截器
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;
}
拦截器的添加
- 插件生成的时机就是调用pluginAll()方法的时候,其中形参target就是Executor,ParameterHandler,ResultSetHandler,StatamentHandler接口的实现类,遍历拦截链InterceptorChain中的所有拦截器,如果匹配则创建target的代理类;拦截链中多个拦截器生成的代理类是嵌套结构,也就是说对前面拦截器生成的代理类再进行代理生成代理类的代理类;
- 调用拦截器中的plugin()方法进行匹配,如果匹配则生成相应的代理类;
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
- plugin()方法主要是为Executor,ParameterHandler,ResultSetHandler,StatamentHandler的实现类生成代理,从而在调用这几个类的方法时候,直接调用InvocationHandler的invoke()方法;
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
- plugin方法中调用MyBatis提供的现成的生成代理的方法Plugin.wrap()方法
- 获取该拦截器的符合要求的所有方法签名
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> 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;
}
- 调用getSignatureMap()方法获取方法签名,遍历所有Signature标签内容,获取type(Executor.class)下所有满足method的方法,并构建方法签名映射;
- 方法签名映射是一个Map
{interface org.apache.ibatis.executor.statement.StatementHandler=[public abstract int org.apache.ibatis.executor.statement.StatementHandler.update(java.sql.
Statement) throws java.sql.SQLException, public abstract java.util.List org.apache.ibatis.executor.statement.StatementHandler.query(java.sql.Statement,org.apache.
ibatis.session.ResultHandler) throws java.sql.SQLException]}
private static Map, Set> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map, Set> signatureMap = new HashMap, Set>();
for (Signature sig : sigs) {
Set methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet();
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;
}
- 获取SignatureMap方法标签映射后,通过type(Executor.class)获取target的所有接口,如果方法签名中有这个接口,则添加到interfaces列表中;
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
Set> interfaces = new HashSet>();
while (type != null) {
for (Class> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[interfaces.size()]);
}
- 如果当前传入的Target的接口中有有@Interceptors注释中定义的接口,则生成代理否则原target返回;
- 实现代理后的Executor等当调用其方法时候会先调用Plugin的invoke()方法,查询该方法是否需要被代理,换句话说就是Executor、ParameterHandler、ResultSetHandler、StatementHandler,在@Intercepts注解中定义了要拦截哪些方法签名。
- 如果当前调用的方法的方法签名在方法签名集合中则表示需要被代理,则调用拦截器的intercept()方法;否则不需要代理则执行原方法;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set 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);
}
}
- 拦截器的逻辑实现在intercept()方法中
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
}finally {
long end = System.currentTimeMillis();
System.out.println("cost time:"+(end-start));
}
}
(3) 总结
- 插件机制的实现主要分为以下几步:
- Configuration初始化期间解析Plugins标签加入拦截链InterceptorChain
- 执行SQL过程中在Executor,ParameterHandler初始化的时候调用pluginAll()方法生成相应的代理类;
- 执行Executor,ParameterHandler等类的方法时候会执行相应的代理类的方法;
- Configuration初始化期间解析Plugins标签加入拦截链InterceptorChain
- 在调用pluginAll()方法生成代理类的时候会遍历拦截链列表中所有的拦截器,层层嵌套生成代理类;配置文件中越靠下的插件配置,越在嵌套代理类的外层;
- 【mybatis|深入理解MyBatis(五)—MyBatis的插件机制】层层代理会影响系统性能,因此要减少没有必要的代理;
推荐阅读
- 深入理解Go之generate
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 由浅入深理解AOP
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- mybatisplus如何在xml的连表查询中使用queryWrapper
- mybatisplus|mybatisplus where QueryWrapper加括号嵌套查询方式
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- 逻辑回归的理解与python示例
- 【1057快报】深入机关,走下田间,交通普法,共创文明