這里的Plugin不是開發(fā)工具中集成的插件队魏,而是Mybatis中額外擴展功能使用到的插件公般,這個類所在的包都在plugin包下。這個原理比較簡單胡桨,總共有7個類在該包下官帘,其中一個PluginException
異常類,拋出相關(guān)插件異常的昧谊。兩個注解Intercepts
與Signature
刽虹,其余四個類實現(xiàn)相關(guān)功能。插件實現(xiàn)相關(guān)功能的主要是其中的接口Interceptor
呢诬,實現(xiàn)了該接口涌哲,然后將其注入到容器中胖缤,就能修改每條sql執(zhí)行時的動作,如果操作不好阀圾,容易出現(xiàn)意想不到的結(jié)果哪廓,所以操作需要慎重。
-
Interceptor
實現(xiàn)該接口即可攔截所有的sql操作 -
Intercepts
注解初烘,用于注解到插件類上涡真,參數(shù)決定攔截哪些類哪些方法 -
Signature
注解,與Intercepts搭配使用 -
Plugin
輔助實現(xiàn)一些識別肾筐,判斷及生成代理對象相關(guān)功能
注入容器
public MapperAutoConfiguration(MybatisProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
這里還是上篇介紹的MapperAutoConfiguration
類哆料,通過配置注解,該類隨著spring boot啟動而注入吗铐,而容器中實現(xiàn)了Interceptor
的所有類都會作為一個列表注入到容器中东亦。
在MapperAutoConfiguration
生成SqlSessionFactory
的Bean時調(diào)用
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
而SqlSessionFactory
在構(gòu)建時調(diào)用了如下代碼保存相關(guān)插件,其實最終是保存在了配置類Configuration
中
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
該類中保存了幾乎絕大部分mybatis需要用到的東西抓歼,在spring容器中讥此,Configuration
是單例,相關(guān)插件會保存到如下屬性中谣妻。
protected final InterceptorChain interceptorChain = new InterceptorChain();
而InterceptorChain
內(nèi)部很簡單萄喳,一個List用來保存相關(guān)插件,而該類也在plugin包下蹋半,是剛才提到的7個類之一他巨。
插件實現(xiàn)Interceptor
接下來看如果實現(xiàn)相關(guān)接口,制作一個插件减江。該接口也很簡單染突,內(nèi)部代碼如下,沒有過多說明辈灼。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
實現(xiàn)類
@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;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
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>
spring boot中可以通過添加@Component
注解份企,則該插件會自動注入,但是不能使用@Configuration
注入,否則不能正常使用巡莹。
插件攔截過程
上邊的代碼會在Executor
類執(zhí)行所有的update操作時都經(jīng)過此插件司志。而可以攔截的type類型有四種:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
具體調(diào)用過程可以查看Configuration
在創(chuàng)建這些對象是如何執(zhí)行。這四個類中都執(zhí)行了如下代碼
executor = (Executor) interceptorChain.pluginAll(executor);
具體的入?yún)⒑头祷刂凳沁@四個類中的之一降宅。然后在InterceptorChain
中的pluginAll方法實際是循環(huán)調(diào)用了所有插件的plugin方法骂远。也就是說以上四個類的所有方法都會經(jīng)過我們插件中的plugin方法。而Plugin.wrap(target, this)這個方法才是識別相關(guān)注解中的類和方法腰根。該方法會判斷當前target是否為注解需要攔截的對象類型激才,如果是,為其生成相關(guān)代理對象返回。
生成的代理對象調(diào)用的處理程序代碼即為Plugin
類中的invoke方法
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);
}
}
如果執(zhí)行的方法是編寫插件類注解中需要攔截的方法瘸恼,則調(diào)用插件中的intercept方法劣挫,否則按原方法執(zhí)行。
這里Interceptor
接口中的兩個方法intercept和plugin,plugin用于替換攔截到的類钞脂,而intercept方法則是替換原有執(zhí)行方法揣云。這里代理執(zhí)行有點繞,別的還好理解冰啃。