鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如sql重写之类
鲁春利的工作笔记,好记性不如烂笔头
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如sql重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
代理链的生成
Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。
下面以Executor为例。Mybatis在创建Executor对象时:
org.apache.ibatis.session.SqlSessionFactory.openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(execType, level, false) {
final Executor executor = configuration.newExecutor(tx, execType);
}
说明:org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。
org.apache.ibatis.session.Configuration
package org.apache.ibatis.session;
public class Configuration {
protected Environment environment;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
// 默认的Executor
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。
protected final InterceptorChain interceptorChain = new InterceptorChain();
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;
}
}
org.apache.ibatis.plugin.InterceptorChain
package org.apache.ibatis.plugin;
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
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);
}
}
下面以一个简单的例子来看看这个plugin方法里到底发生了什么。
package com.invicme.common.persistence.interceptor;
import java.sql.Connection;
import java.lang.reflect.Method;
import java.util.Properties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Intercepts(value = {
@Signature(type=Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type=StatementHandler.class, method="prepare", args={Connection.class})
})
public class SamplePagingInterceptor implements Interceptor {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
logger.info("target is {}", target.getClass().getName());
Method method = invocation.getMethod();
logger.info("method is {}", method.getName());
Object[] args = invocation.getArgs();
for (Object arg : args) {
logger.info("arg is {}", arg);
}
Object proceed = invocation.proceed();
logger.info("proceed is {}", proceed.getClass().getName());
return proceed;
}
@Override
public Object plugin(Object target) {
logger.info("target is {}", target.getClass().getName());
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String value = properties.getProperty("sysuser");
logger.info("value is {}", value);
}
}
每一个拦截器都必须实现上面的三个方法,其中:
1) Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
2) Object plugin(Object target)就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。
3) setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。
注解里描述的是指定拦截方法的签名 [type, method, args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。
代理链的生成
为了更好的理解后面的内容,穿插一个Java反射的示例
package com.invicme.common.aop.proxy;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.PluginException;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.invicme.common.persistence.interceptor.SamplePagingInterceptor;
public class MainDriver {
private static Logger logger = LoggerFactory.getLogger(MainDriver.class);
public static void main(String[] args) {
SamplePagingInterceptor interceptor = new SamplePagingInterceptor();
wrap(interceptor);
}
public static Object wrap (SamplePagingInterceptor interceptor) {
// 获取拦截器对应的@Intercepts注解
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
{
for (Iterator<Class<?>> it = signatureMap.keySet().iterator(); it.hasNext(); ) {
Class<?> clazz = it.next();
Set<Method> set = signatureMap.get(clazz);
logger.info("【Signature】clazz name is {}", clazz.getName());
for (Iterator<Method> itt = set.iterator(); itt.hasNext(); ) {
Method method = itt.next();
logger.info("\tmethod name is {}", method.getName());
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
logger.info("\t\tparam is {}", type);
}
}
}
}
logger.info("================================================================");
{
Configuration configuration = new Configuration();
Executor executor = configuration.newExecutor(null);
Class<?>[] interfaces = executor.getClass().getInterfaces();
for (Class<?> clazz : interfaces) {
logger.info("【Interface】clazz name is {}", clazz);
}
}
return null;
}
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[] sigs = interceptsAnnotation.value();
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;
}
}
继续进行Plugin代码的说明。
从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private 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) {
// 生成代理对象,Plugin对象为该代理对象的InvocationHandler
//(InvocationHandler属于java代理的一个重要概念,不熟悉的请参考Java动态代理)
// Http://luchunli.blog.51cto.com/2368057/1795921
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);
}
}
}
这个Plugin类有三个属性:
private Object target; // 被代理的目标类
private Interceptor interceptor; // 对应的拦截器
private Map<Class<?>, Set<Method>> signatureMap; // 拦截器拦截的方法缓存
关于InvocationHandler请参阅:Java动态代理
我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对executor执行了多次plugin(org.apache.ibatis.plugin.InterceptorChain)。
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
说明:
拦截器在解析MyBatis的配置文件时已经add到了该interceptors拦截器中。
org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。
过程分析:
第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象;
第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...;
这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。
代理链的拦截
代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,它(org.apache.ibatis.plugin.Plugin)的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)) {
// 调用代理类所属拦截器的intercept方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
在invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,这个对象是什么样子的,他的功能又是什么呢?
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Invocation {
private Object target;
private Method method;
private Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
// 拦截器最终调用的方法,实际还是通过反射来调用的
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。
在每个拦截器的intercept方法内,最后一个语句一定是invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。
在MyBatis中配置的拦截器方式为:
<plugins>
<plugin interceptor="com.invicme.common.persistence.interceptor.SamplePagingInterceptor"></plugin>
<plugin interceptor="com.invicme.common.persistence.interceptor.PreparePaginationInterceptor"></plugin>
</plugins>
我们假设在MyBatis配置了一个插件,在运行时会发生什么?
1) 所有可能被拦截的处理类都会生成一个代理
2) 处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法
3) 执行插接中的拦截方法后,推进目标的执行
如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。
--结束END--
本文标题: Mybatis之插件原理
本文链接: https://lsjlt.com/news/38926.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-10-23
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0