写在前面
想要系统学习的,可以参考这篇文章,重要!!!。
一个框架之所以优秀,除了本身提供的强大的既有功能之外,还允许根据自己特定的需求和业务场景来进行扩展,mybatis同样提供了这样的功能,是通过插件功能来实现的,对应的是org.apache.ibatis.plugin包,如下图:

其中org.apache.ibatis.plugin.Interceptor是插件对应的接口,源码如下:
org.apache.ibatis.plugin.Interceptor
public interface Interceptor {
// 执行拦截的方法,我们可以在这里添加扩展逻辑
Object intercept(Invocation invocation) throws Throwable;
// 应用目标对象,返回代理对象
Object plugin(Object target);
// 设置在<plugin>标签中配置的<property>子标签们,如下可能配置
/*
<plugins>
<plugin interceptor="yudaosourcecode.mybatis.interceptor.MyInterceptor">
<property name="xxx" value="yyyy"/>
<property name="aaaa" value="bbbb"/>
</plugin>
</plugins>
*/
// 即自定义的属性信息
void setProperties(Properties properties);
}
1:一个小例子
我们先来实现这样的效果,通过这里的API实现:生成java.util.Map的动态代理对象,当调用其get方法时不管目标key的实际值时什么,都返回“dongshidaddy”,。
首先定义实现类:
@Intercepts({
@Signature(type = java.util.Map.class, method = "get", args = Object.class)
})
public class MyMapProxy implements Interceptor {
// org.apache.ibatis.plugin.Invocation是一个封装目标类,目标方法,目标参数的对象,如下:
/*
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);
}
}
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 直接返回dongshidaddy,不管目标真实值时什么
return "dongshidaddy";
}
@Override
public Object plugin(Object target) {
// 通过org.apache.ibatis.plugin.Plugin的API生成目标对象的代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
测试代码:
public static void main(String[] args) throws Exception {
// MyMapProxy
Map<String, String> myMap = new HashMap<>();
myMap.put("name", "jack");
myMap.put("age", "90");
Map myMapAfterProxy = (Map)new MyMapProxy().plugin(myMap);
System.out.println("通过原始对象获取实际name和age:");
System.out.println("name: " + myMap.get("name"));
System.out.println("age: " + myMap.get("age"));
System.out.println("通过代理对象获取实际name和age:");
System.out.println("name: " + myMapAfterProxy.get("name"));
System.out.println("age: " + myMapAfterProxy.get("age"));
}
运行如下:
通过原始对象获取实际name和age:
name: jack
age: 90
通过代理对象获取实际name和age:
name: dongshidaddy
age: dongshidaddy
MyMapProxy类实现了org.apache.ibatis.plugin.Intercetor接口,这是mybaits为了让我们自定义插件而定义的规范接口接口先行吗,然后通过注解@org.apache,ibatis.plugin.Intercepts定义了要拦截的类,方法,参数等信息,如我们的定义@Signature(type = java.util.Map.class, method = "get", args = Object.class),含义如下:
@Signature:
定义方法的签名,即唯一确定要拦截的方法。
type = java.util.Map.class:
要拦截的class类型是java.util.Map.class。
method = "get":
要拦截的方法名称是get。
args=Object.class:
方法的参数是Object.class。
这样子当我们调用Plugin.wrap(target, this)方法的时候就会通过这里的配置生成对应的代理对象了,而生成的代理对象拦截目标方法的方法就是org.apache.ibatis.plugin.Interceptor#intercept,这样就达到了拦截方法的目的了,就可以在拦截的逻辑里增加自定义内容了。
关于wrap方法生成代理对象,具体参考2.1:wrap生成代理对象。
2:Plugin
2.1:wrap生成代理对象
源码如下:
org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
// 获取@org.apache.ibatis.plugin.Intercepts注解配置要拦截的方法信息
// 2021-08-31 11:21:40
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取需要代理类的class类型
Class<?> type = target.getClass();
// 根据需要代理类实现的所有接口,用于通过jdk动态代理API生成代理类
// 2021-08-31 13:34:13
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果是实现了接口则生成代理类
if (interfaces.length > 0) {
// 通过java.lang.reflect.Proxy的newProxyInstance静态方法生成代理类
// 2021-08-31 14:37:43
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
2021-08-31 11:21:40处是获取注解配置的要拦截方法信息,如下配置:
@Intercepts({
@Signature(type = java.util.Map.class, method = "get", args = Object.class),
@Signature(type = java.util.Map.class, method = "put", args = { Object.class, Object.class }),
@Signature(type = java.util.List.class, method = "add", args = { Object.class }),
@Signature(type = java.util.List.class, method = "contains", args = { Object.class })
})
则结果如下图:

其中key是@org.apache,ibatis.plugin.Signature注解配置的type属性值,值是method和args组合对应的方法Method对象信息,具体参考2.1.1:解析拦截方法信息。2021-08-31 13:34:13处是获取需要代理的类所有父接口,具体参考2.1.2:获取父接口。2021-08-31 14:37:43处是通过反射包API生成代理类,其中的org.apache.ibatis.plugin.Plugin的构造函数源码如下:
org.apache.ibatis.plugin.Plugin
// 实现了java.lang.reflect.InvocationHandler接口
public class Plugin implements InvocationHandler {
// 被代理的目标类
private Object target;
// 实现了org.apache.ibatis.plugin.Interceptor接口的类,即我们自定义的mybaits的插件类
private Interceptor interceptor;
// 在自定义插件类上的@org.apache.ibatis.plugin.Intercepts注解配置的方法信息
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;
}
...snip...
}
传到jdk动态代理的InvocationHandler的实现是org.apache.ibatis.plugin.Plugin因此会调用invoke方法进行方法调用,具体参考2.1.3:invoke。
2.1.1:解析拦截方法信息
源码如下:
org.apache.ibatis.plugin.Plugin#getSignatureMap
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 从class信息中获取@org.apache.ibatis.plugin.Intercepts注解信息
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// 如果是没有配置@org.apache.ibatis.plugin.Intercepts则直接异常
// 因为需要依赖于该注解信息生成代理类,所以必须有
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
// 调用value方法获取@org.apache.ibatis.plugin.Signature签名注解数组信息
// 用于定义要拦截的方法的签名信息
Signature[] sigs = interceptsAnnotation.value();
// 定义用于存放结果的map
// key->类的class,value->类下要拦截的方法的集合
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
// 循环处理
for (Signature sig : sigs) {
// 根据当前类class获取方法数组,可能存在也可能不存在
Set<Method> methods = signatureMap.get(sig.type());
// 不存在的话,则初始化一个
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
// 根据方法的名称和参数信息,获取方法对应的java.lang.reflect.Method对象
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;
}
2.1.2:获取父接口
源码如下:
org.apache.ibatis.plugin.Plugin#getAllInterfaces
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
// 结果集合
Set<Class<?>> interfaces = new HashSet<Class<?>>();
// 循环type
while (type != null) {
// 获取所有的父接口,因为java可以实现多个接口,所以这里是一批
for (Class<?> c : type.getInterfaces()) {
// 必须在@org.apache.ibatis.plugin.Intercepts注解中配置的类
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
// 因为java是单继承,所以这里只可能有一个父类
type = type.getSuperclass();
}
// 转Class数组返回
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
截止到这里,mybaits中是如何加载这些自定义插件还没有分析到,其中定义插件和注册插件的方式可以参考这里。
定义完插件之后,需要在全局配置文件中通过plugin标签进行配置,可能如下:
<plugins>
<plugin interceptor="yudaosourcecode.mybatis.interceptor.MyInterceptor">
<property name="xxx" value="yyyy"/>
<property name="aaaa" value="bbbb"/>
</plugin>
</plugins>
在mybatis启动的过程中,首先是加载全局配置文件的这些信息,具体参考3.1:读取插件信息。
2.1.3:invoke
源码如下:
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());
// 如果是有声明拦截的方法,并且当前方法包含在要拦截的方法集合中,则调用intercept方法进行拦截
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);
}
}
3:mybatis插件加载过程
3.1:读取插件信息
会执行到方法org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration,源码如下:
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
...snip...
// 解析<plugins/>标签
// 2021-08-31 14:56:15
pluginElement(root.evalNode("plugins"));
...snip...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2021-08-31 14:56:15处源码如下:
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 获取所有的<plugin/>子标签
for (XNode child : parent.getChildren()) {
// 获取interceptor属性值,即自定义的插件实现类全限定名称
String interceptor = child.getStringAttribute("interceptor");
// 获取所有的属性
Properties properties = child.getChildrenAsProperties();
// 强转
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 调用org.apache.ibatis.plugin.Interceptor的setProperties方法设置属性信息
interceptorInstance.setProperties(properties);
// 添加自定义插件信息
// 2021-08-31 14:59:02
configuration.addInterceptor(interceptorInstance);
}
}
}
2021-08-31 14:59:02处是添加自定义插件到全局配置文件中,源码如下:
org.apache.ibatis.session.Configuration#addInterceptor
public void addInterceptor(Interceptor interceptor) {
// protected final InterceptorChain interceptorChain = new InterceptorChain();
// 2021-08-31 15:00:12
interceptorChain.addInterceptor(interceptor);
}
2021-08-31 15:00:12处是添加自定义插件到拦截器链中,关拦截器链可以参考4.1:添加自定义插件。另外在调用器链org.apache.ibatis.plugin.InterceptorChain中有一个非常重要的方法pluginAll用来生成代理类,从而通过代理类干预mybatis正常的执行流程,如sql语句的生成,结果集的生成等。关于该方法,具体参考4.2:pluginAll。
4:InterceptorChain
4.1:添加自定义插件
org.apache.ibatis.plugin.InterceptorChain#addInterceptor
public void addInterceptor(Interceptor interceptor) {
// 添加到interceptors集合中
// org.apache.ibatis.plugin.InterceptorChain#interceptors
// private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
interceptors.add(interceptor);
}
4.2:pluginAll
源码如下:
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 2021-08-31 15:54:06
target = interceptor.plugin(target);
}
return target;
}
一般2021-08-31 15:54:06处我们都是通过方法Plugin.wrap来创建代理对象,该方法具体参考2.1:wrap生成代理对象,接下来我们看下都有哪些地方使用到了该方法,如下:

结合2.1:wrap生成代理对象处的只有当被代理类在@org.apache.ibatis.plugin.Interceptors注解中配置了的时候才会生成代理对象,否则还是返回对象本身,因此mybaits支持配置的接口如下:
org.apache.ibatis.executor.Executor->执行器,用来调用StatementHandler执行数据库操作
org.apache.ibatis.executor.statement.StatementHandler->用来调用JDBC的java.sql.Statement的处理器
org.apache.ibatis.executor.parameter.ParameterHandler->用来设置参数信息的处理器
org.apache.ibatis.executor.resultset.ResultSetHandler->用来处理结果集的处理器
因此,通过对这里的接口生成代理,我们就可以进行一些mybatis的扩展工作了,比如添加分页插件,关于分页插件的应用可以参考这篇文章。
本文详细介绍了如何利用Mybatis插件功能,实现在调用Map的get方法时统一返回预设值。通过自定义Interceptor接口和@Intercepts注解,展示了如何创建代理对象并拦截特定方法。重点讲解了Plugin、InterceptorChain和配置插件的过程。

5226

被折叠的 条评论
为什么被折叠?



