1、Interceptor
1.1、接口源码分析如下:
package org.apache.ibatis.plugin;
import java.util.Properties;
/**
* MyBatis允许用户使用自定义拦截器对SQL语句执行的某一点进行拦截。
* 默认情况下,MyBatis允许拦截器拦截Excerutor的方法、ParameterHandler
* 的方法、ReslultSetHanlder的方法以及StatementHandler的方法。具体拦截
* 的方法入下:
* 1)Excutor中的update()方法、query()方法、flushStatement()方法、commit()方法、
* rollback()方法、getTransaction()方法、close()方法、isClosed()方法。
* 2)ParameterHadler中的getParameterObject()方法、setParameters()方法。
* 3)ResultSetHandler中的handlerResultSets()方法、handleOutputParamenters()方法。
* 4)SatementHandler中的prepare()方法、parameterize()方法、batch()方法、update()方法、
* query()方法。
*
* @author Clinton Begin
*/
public interface Interceptor {
// 执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable;
// 决定是否触发 intercept ()方法
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 根据配置初始化 Interceptor 对象
default void setProperties(Properties properties) {
// NOP
}
}
1.2、自定义使用的方法:
用户自定义的拦截器除了继承Interceptor接口,还需要使用@Intercepts和@Signature两个注解进行标识。@Intercepts注解中指定了一个@Signature注解列表,每个注解中都标识了该插件需要拦截的方法信息,其中@Signature注解的type属性指定需要拦截的类型,method属性指定需要拦截的方法,args属性指定了被拦截方法的参数列表。通过这三个属性值,@Signature注解就可以表示一个方法签名,唯一确定一个方法。如下所示,该拦截器需要拦截Executor接口的两个方法,分别是query和close方法。
@Intercepts({
@Signature(type =Executor . class , method =”query”, args = {
MappedStatement.class , Object.class , RowBounds.class ,
ResultHandler . class}) ,
@Signature ( type =Executor . class , method = ” close”, args = {boolean.class}
})
public class ExamplePlugin implements Interceptor {
private int testProp ; //省略该属性的 getter/setter 方法
// .. .其他方法在后面详细介
}
定义完成一个自定义拦截器之后,需要在mybatis-config.xml配置文件中对该拦截器进行配置,如下所示:
<plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>
1.3、加载和使用原理分析
在MyBatis初始化时,会通过XMLConfigBuilder.pluginElement()方法解析mybatis-config.xml配置文件中定义的节点,得到相应的Interceptor对象以及调用Interceptor.setProperties(properties)方法配置相应属性,最后将Interceptor对象添加到Configuration.interceptorChain字段中保存。
// mybatis插件列表
protected final InterceptorChain interceptorChain = new InterceptorChain();
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).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
// Configuration.java
完成InterceptoConfiguration.javar的加载操作之后,在MyBatis中使用的这四类对象,都是通过Configuration.new*()系列方法创建的。如果配置了用户自定义拦截器,则会在该系列方法中,通过InterceptorChain.pluginAll()方法为目标对象创建代理对象,所以通过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对象,实际由statementType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
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);
}
// cacheEnabled默认是true,传入的参数是SimpleExecutor,装饰成CachingExecutor
if (cacheEnabled) {// 如果配置了二级缓存
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
// Configuration.java
InterceptorChain.pluginAll()源码如下:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);// 调用Interceptor.plugin方法生成代理对象
}
return target;
}
在Interceptor接口中有默认方法plugin,它使用了Mybatis提供的Plugin工具类来生成代理类,整个类源码分析如下:
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;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;// 目标对象
private final Interceptor interceptor;// Interceptor 对象
private final Map<Class<?>, Set<Method>> signatureMap;// 记录 @Signature 注解中的信息
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) {
// 获取用户自定义 Interceptor Signature 注解的信息,
// getSignatureMap ()方法负责处理@Signature 注解
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();// 获取目标类型
// 获取 目标类型实现的接口,正如前文所述,拦截器可以拦截的 类对象都实现了相应的接口,这也是能
// 使用 JDK 动态代理的方式创建代理对象的基础
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {// 使用 JDK 动态代理的方式创建代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 因为Plugin实现了InvocationHandler对象
new Plugin(target, interceptor, signatureMap));
}
return target;
}
/**
* Plugin.invoke() 方法中,会将当前调用的方法与 signatureMap 集合中记录的方法信息进行
* 比较,如果当前调用 的方法是需要被拦截的方法,则调用其 intercept()方法进行处理,如果不能
* 被拦截 直接调用 target 的相应方法。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如采当前调用的方法需妥被拦截,则调用Interceptor.intercept()方法进行拦截处理
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
// 如采当前调用的方法不能被拦截 9J1J 调用 target 对象的相应方法
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[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> 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()]);
}
}
2、应用场景分析
2.1、分页插件
使用MyBatis-PageHelper来分析,请查看下一小节。