Mybatis插件使用和原理
Mybatis插件
一、官方说明
官方文档中在插件章节也进行了说明:
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 【重写SQL】
- ParameterHandler (getParameterObject, setParameters) 【处理参数】
- ResultSetHandler (handleResultSets, handleOutputParameters) 【处理结果集】
- StatementHandler (prepare, parameterize, batch, update, query) 【参数、结果集、sql都可处理】
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
二、官方实例
mybatis还贴心的给了一个例子:
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
在全局配置文件中来进行声明:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
在property中,还可以来针对插件信息来做一个补充说明机制,添加额外信息可以让插件类使用。
这个地方很有用,mybatis-plus中就利用到了这里的配置信息,比如动态表、逻辑删除等等操作。
三、Configuration创建插件
四大拦截器对象都是由configurataion对象创建出来的,所以configurataion有权来对其做改造。
public StatementHandler newStatementHandler
public Executor newExecutor
public ResultSetHandler newResultSetHandler
public ParameterHandler newParameterHandler
1、Mybatis对插件的解析
先将官方给的实例拿下来:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
直接来到org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
pluginElement(root.evalNode("plugins"));
具体实现:
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取得到插件配置标签
String interceptor = child.getStringAttribute("interceptor");
// 获取得到Properties中的配置信息
Properties properties = child.getChildrenAsProperties();
// 创建拦截器示例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 回调setProperties放阿飞
interceptorInstance.setProperties(properties);
// 添加拦截器
configuration.addInterceptor(interceptorInstance);
}
}
}
首先拿到插件的全限定类名,然后获取得到子属性,也就是property中的name和value
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
将属性和值保存到properties中。然后直接创建插件对象,并将属性设置到实例中去。
然后将插件加入到configuration对象中的拦截器中,最终保存到InterceptorChain类中的
private final List<Interceptor> interceptors = new ArrayList<>();
保存到结合中来。
2、使用插件的地方
以Executor为例:
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;
}
注意下面这行代码:
将当前的执行器传进去,然后进行判断
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 从这里可以看到代理逻辑
target = interceptor.plugin(target);
}
return target;
}
然后来到接口中:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
// 可以手动重写
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
一般来说,plugin会来判断是哪种类型,然后进行代理。下面看下代理逻辑:
public static Object wrap(Object target, Interceptor interceptor) {
// 解析@Intercepts和@Signature,哪些方法需要被拦截
Map<Class<?>, Set<Method>> 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;
}
看到了动态代理,那么就意味着,有对应的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)) {
// 调用拦截方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
来到:
public class InterceptorChain {
private final List<Interceptor> 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<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
首先循环遍历插件集合,然后调用插件方法
然后会执行到:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
所以重点看下这里的Plugin.wrap方法做了什么事情:
点进去看了之后,发现很简单,动态代理方式。
public static Object wrap(Object target, Interceptor interceptor) {
// 获得interceptor配置的@Signature的type
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 当前代理类型
Class<?> type = target.getClass();
// 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
使用了动态代理来将四大插件对象给替换成了动态代理对象,而在执行执行方法的时候,会来执行Plugin中的invoke方法
看下实现过程:
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);
}
}
然后就会执行到我们自定义中的分页插件中的intercept方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
对于setProperties来说,子类中可以自定义属性来进行保存。而对于intercept则是用户根据自己的需求来进行定义的。
3、简化步骤
在实现intercept方法的时候,如果自定义逻辑执行完成,那么该让程序继续向下执行的时候,不需要我们再来进行
method.invoke(target,args);
我们可以直接调用intercept方法中的参数Invocation,因为在Invocation中封装了一个方法:
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
直接来进行调用即可
四、SpringBoot配置
配置插件方式一:
@Configuration
public class MybatisConfig {
// 注册插件方式1
@Bean
public EditPlugin myPlugin() {
// 注册成bean就意味着可以从容器中获取得到bean进行操作
return new EditPlugin();
}
}
配置插件方式二:
@Configuration
public class MybatisConfig {
// // 注册插件方式2
// @Bean
// public ConfigurationCustomizer configurationCustomizer() {
// return new ConfigurationCustomizer() {
// @Override
// public void customize(org.apache.ibatis.session.Configuration configuration) {
// //插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
// EditPlugin myPlugin = new EditPlugin();
// configuration.addInterceptor(myPlugin);
// }
// };
// }
// 注册插件方式2
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
//插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
EditPlugin myPlugin = new EditPlugin();
configuration.addInterceptor(myPlugin);
};
}
}
五、使用场景
1、删除表缓存;
2、分表查:表1查不到;从表二查询;
3、动态插表和查表;