MyBatis插件原理解析及自定义插件实践
Posted 敲代码的小小酥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis插件原理解析及自定义插件实践相关的知识,希望对你有一定的参考价值。
一、插件原理解析
首先,要搞清楚插件的作用。不管是我们自定义插件,还是用其他人开发好的第三方插件,插件都是对MyBatis的四大核心组件:Executor,StatementHandler,ParameterHandler,ResultSetHandler来进行增强的,利用动态代理的技术,来增强框架的方法,来满足我们特殊的业务需求。
1.先看几个重要的类:
package org.apache.ibatis.plugin;
import java.util.Properties;
/**
* @author Clinton Begin
*/
/**
所有的插件,都要实现这个接口。
*/
public interface Interceptor
//这个方法写增强逻辑
Object intercept(Invocation invocation) throws Throwable;
//生成代理对象,生成四大核心组件的代理类。通过Plugin.warp(target,this)来生成
Object plugin(Object target);
//读取MyBatis配置文件中定义插件拦截器时配置的一些属性
void setProperties(Properties properties);
再看intercept方法中参数对象Invocation源码:
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author Clinton Begin
*/
public class Invocation
//要生成代理的对象
private final Object target;
//目标方法,即要增强的方法
private final Method method;
//增强方法的参数
private final 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);
上面提到,插件使用的是jdk动态代理技术,MyBatis定义了Plugin类实现了InvocationHandler接口,看源码:
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插件实现类的@Signature注解
private final 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)
//JDK动态代理
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
//获取@Signature注解中method的值
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);
//解析@Signature参数
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;
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap)
Set<Class<?>> interfaces = new HashSet<Class<?>>();
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()]);
可见,Plugin类的wrap方法生成代理类,invoke方法执行代理方法。Plugin中有解析@Signature,看@Signature的源码:
package org.apache.ibatis.plugin;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target()
public @interface Signature
//四大核心组件类中的一个,定义哪个类,那么插件就是要加强哪个类的方法
Class<?> type();
//要拦截的方法,type定义的哪个组件,这里就要写该组件里对应的要加强的方法
String method();
//method所写方法的参数,可以定位到重载方法的某一个
Class<?>[] args();
2.开发插件基本套路
介绍了插件有关的几个重要的类,下面就用这几个类,演示一下插件开发的基本套路。
首先,定义插件类,实现Interceptor接口,并定义@Signature 说明要拦截的类和方法,如下:
@Intercepts(
@Signature(
type = Executor.class,//拦截Executor类
method = "update",//拦截update方法
args = MappedStatement.class,Object.class //update方法的参数
)
)
public class MyBatisPlugin implements Interceptor
@Override
public Object intercept(Invocation invocation) throws Throwable
System.out.println("拦截的目标对象:"+invocation.getTarget());
System.out.println("写加强逻辑");
return invocation.proceed();//执行原方法
@Override
public Object plugin(Object target)
//生成代理对象
return Plugin.wrap(target,this);
@Override
public void setProperties(Properties properties)
System.out.println("获取配置文件参数");
然后,将该插件注入Spring容器中,即可。如果是spring项目,可以在xml中定义plugins插件,如下:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
如果是SpringBoot项目,如下注入:
@Configuration
public class MybatisPluginsBean
@Bean
public MyBatisPlugin myBatisPlugin()
return new MyBatisPlugin();
由上可见,定义插件只需要两个关键步骤,即定义Interceptor实现类,实现插件逻辑;注入到spring容器中这两步即可。
二、PageHelper插件原理解析
我们结合PageHelper插件,研究一下插件的执行原理。
首先,读取xml文件,并解析plugins标签,然后将配置的interceptor类通过反射生成对象,注入到Spring容器中。这个步骤的代码我们省去,下面看生成插件对象后,执行了哪些操作。
首先,生成插件对象后,在Configuration类中,将插件对象加入到了拦截链interceptorChain中:
然后看一下,PageInterceptor类,是对Executor组件的query方法进行了拦截:
那么何时对Executor类进行的增强呢,我们知道,插件的原理是代理,那么代理对象何时生成呢,一定是在创建Executor对象的时候,生成代理对象,总不能先生成元对象,执行了一半儿,再生成代理对象吧,那容器中岂不是有了两个Executor对象,破坏了单例规则吗,对吧。所以看Executor创建的地方:
Executor是在SqlSession创建的时候被创建的,SqlSession是在SqlSessionFactory中的openSession方法中创建的,所以从openSession中点进去,查看,最终点到:
看pluginAll方法:
public Object pluginAll(Object target)
for (Interceptor interceptor : interceptors)
target = interceptor.plugin(target);
return target;
可见,遍历拦截链,然后调用plugin方法,获得代理对象。interceptor的plugin最终会调用Plugin的wrap方法,该方法在第一部分已经分析过,最终,Executor就返回了代理对象。
那么在接下来执行Executor方法的时候,就会调用Plugin类的invoke方法,如果是加强的方法,则走PageInterceptor的intercept方法,进行分页增强,如果没有拦截的方法,则执行原方法即可。这就是分页插件的执行流程。
**总结:**由上面的流程分析可以知道,插件对象是在四大组件的类生成时,生成的代理对象。通过Configuration中的拦截链属性,判断是否要生成代理对象。而代理对象方法执行,由Plugin类来完成。我们只需定义Interceptor的实现类,来做逻辑代码,而插件的原生方法还是加强方法的执行,是由Plugin类支撑的,也就是Plugin是Interceptor的底层实现。
三、自定义插件实践
1.屏蔽敏感字插件实现
在某些业务场景中,涉及到一些敏感字,需要用*号代替。比如身份证号,手机号中间部分,都要用星号代替,而不是展示全部。我们正常的实现思路是查询出数据后,再做处理,把有身份证号的模块做一下处理,再把有手机号的模块做一下处理。假如这种模块很多的话,那我们需要改很多代码。
这个问题,就可以用插件的方式解决。屏蔽关键字,是对查询结果的处理,在MyBatis中对应的就是ResultSetHandler组件。用插件生成ResultSetHandler的代理对象,在ResultSet中对字段进行屏蔽,不就避免了按功能模块进行屏蔽的繁琐了吗 。
实现思路:
对于开发插件而言,只需关注两步,1.实现Interceptor接口,写插件逻辑 2.注册插件。
首先,拦截ResultSetHandler类的handleResultSets方法,该方法对返回值进行处理。
@Intercepts(
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = Statement.class
)
)
public class MyBatisPlugin implements Interceptor
@Override
public Object intercept(Invocation invocation) throws Throwable
//执行原方法
List<Object> records = (List<Object>)invocation.proceed();
//TODO 对返回值records进行加密处理
return records;//执行原方法
@Override
public Object plugin(Object target)
//生成代理对象
return Plugin.wrap(target,this);
@Override
public void setProperties(Properties properties)
System.out.println("获取配置文件参数");
至于TODO部分,就是加密逻辑,这个我们自己实现即可,这里不过多描述。可以自定义注解,有这个注解的字段,就进行加密处理。
2.分表功能实现
见日常开发总结模块
以上是关于MyBatis插件原理解析及自定义插件实践的主要内容,如果未能解决你的问题,请参考以下文章