寫過Mybatis插件?那說說自定義插件是如何加載的吧?

大多數(shù)框架,都支持插件,用戶可通過編寫插件來自行擴(kuò)展功能,Mybatis也不例外。
我們從插件配置、插件編寫、插件運(yùn)行原理、插件注冊(cè)與執(zhí)行攔截的時(shí)機(jī)、初始化插件、分頁插件的原理等六個(gè)方面展開闡述。
1. 插件配置
Mybatis的插件配置在configuration內(nèi)部,初始化時(shí),會(huì)讀取這些插件,保存于Configuration對(duì)象的InterceptorChain中。
configuration?PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"?"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
????<plugins>
??<plugin?interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
???<property?name="value"?value="100"?/>
??plugin>
?plugins>
configuration>
public?class?Configuration?{
????protected?final?InterceptorChain?interceptorChain?=?new?InterceptorChain();
}
org.apache.ibatis.plugin.InterceptorChain.java源碼。
public?class?InterceptorChain?{
??private?final?List?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?getInterceptors()? {
????return?Collections.unmodifiableList(interceptors);
??}
}
上面的for循環(huán)代表了只要是插件,都會(huì)以責(zé)任鏈的方式逐一執(zhí)行(別指望它能跳過某個(gè)節(jié)點(diǎn)),所謂插件,其實(shí)就類似于攔截器。
2. 如何編寫一個(gè)插件
插件必須實(shí)現(xiàn)org.apache.ibatis.plugin.Interceptor接口。
public?interface?Interceptor?{
??
??Object?intercept(Invocation?invocation)?throws?Throwable;
??Object?plugin(Object?target);
??void?setProperties(Properties?properties);
}
intercept()方法:執(zhí)行攔截內(nèi)容的地方,比如想收點(diǎn)保護(hù)費(fèi)。由plugin()方法觸發(fā),interceptor.plugin(target)足以證明。
plugin()方法:決定是否觸發(fā)intercept()方法。
setProperties()方法:給自定義的攔截器傳遞xml配置的屬性參數(shù)。
下面自定義一個(gè)攔截器:
@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?MyBatisInterceptor?implements?Interceptor?{
?private?Integer?value;
?@Override
?public?Object?intercept(Invocation?invocation)?throws?Throwable?{
??return?invocation.proceed();
?}
?@Override
?public?Object?plugin(Object?target)?{
??System.out.println(value);
????????//?Plugin類是插件的核心類,用于給target創(chuàng)建一個(gè)JDK的動(dòng)態(tài)代理對(duì)象,觸發(fā)intercept()方法
??return?Plugin.wrap(target,?this);
?}
?@Override
?public?void?setProperties(Properties?properties)?{
??value?=?Integer.valueOf((String)?properties.get("value"));
?}
}
面對(duì)上面的代碼,我們需要解決兩個(gè)疑問:
為什么要寫Annotation注解?注解都是什么含義?
答:Mybatis規(guī)定插件必須編寫Annotation注解,是必須,而不是可選。
@Intercepts注解:裝載一個(gè)@Signature列表,一個(gè)@Signature其實(shí)就是一個(gè)需要攔截的方法封裝。那么,一個(gè)攔截器要攔截多個(gè)方法,自然就是一個(gè)@Signature列表。
type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }
解釋:要攔截Executor接口內(nèi)的query()方法,參數(shù)類型為args列表。
Plugin.wrap(target, this)是干什么的?
答:使用JDK的動(dòng)態(tài)代理,給target對(duì)象創(chuàng)建一個(gè)delegate代理對(duì)象,以此來實(shí)現(xiàn)方法攔截和增強(qiáng)功能,它會(huì)回調(diào)intercept()方法。
org.apache.ibatis.plugin.Plugin.java源碼:
public?class?Plugin?implements?InvocationHandler?{
??private?Object?target;
??private?Interceptor?interceptor;
??private?Map,?Set>?signatureMap;
??private?Plugin(Object?target,?Interceptor?interceptor,?Map,?Set>?signatureMap) ?{
????this.target?=?target;
????this.interceptor?=?interceptor;
????this.signatureMap?=?signatureMap;
??}
??public?static?Object?wrap(Object?target,?Interceptor?interceptor)?{
????Map,?Set>?signatureMap?=?getSignatureMap(interceptor);
????Class>?type?=?target.getClass();
????Class>[]?interfaces?=?getAllInterfaces(type,?signatureMap);
????if?(interfaces.length?>?0)?{
??????//?創(chuàng)建JDK動(dòng)態(tài)代理對(duì)象
??????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?{
??????Set?methods?=?signatureMap.get(method.getDeclaringClass());
??????//?判斷是否是需要攔截的方法(很重要)
??????if?(methods?!=?null?&&?methods.contains(method))?{
????????//?回調(diào)intercept()方法
????????return?interceptor.intercept(new?Invocation(target,?method,?args));
??????}
??????return?method.invoke(target,?args);
????}?catch?(Exception?e)?{
??????throw?ExceptionUtil.unwrapThrowable(e);
????}
??}
//...
}
Map
所以,我們不要?jiǎng)硬粍?dòng)就說反射性能很差,那是因?yàn)槟銢]有像Mybatis一樣去緩存一個(gè)對(duì)象的反射結(jié)果。
判斷是否是需要攔截的方法,這句注釋很重要,一旦忽略了,都不知道Mybatis是怎么判斷是否執(zhí)行攔截內(nèi)容的,要記住。
知乎砍出正義一刀,PDD祭出終極防御:“供應(yīng)商員工”!輕松化解攻勢(shì)!
3. Mybatis可以攔截哪些接口對(duì)象?
public?class?Configuration?{
//...
public?ParameterHandler?newParameterHandler(MappedStatement?mappedStatement,?Object?parameterObject,?BoundSql?boundSql)?{
????ParameterHandler?parameterHandler?=?mappedStatement.getLang().createParameterHandler(mappedStatement,?parameterObject,?boundSql);
????parameterHandler?=?(ParameterHandler)?interceptorChain.pluginAll(parameterHandler);?//?1
????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);?//?2
????return?resultSetHandler;
??}
??public?StatementHandler?newStatementHandler(Executor?executor,?MappedStatement?mappedStatement,?Object?parameterObject,?RowBounds?rowBounds,?ResultHandler?resultHandler,?BoundSql?boundSql)?{
????StatementHandler?statementHandler?=?new?RoutingStatementHandler(executor,?mappedStatement,?parameterObject,?rowBounds,?resultHandler,?boundSql);
????statementHandler?=?(StatementHandler)?interceptorChain.pluginAll(statementHandler);?//?3
????return?statementHandler;
??}
??public?Executor?newExecutor(Transaction?transaction)?{
????return?newExecutor(transaction,?defaultExecutorType);
??}
??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);?//?4
????return?executor;
??}
//...
}
Mybatis只能攔截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4個(gè)接口對(duì)象內(nèi)的方法。
重新審視interceptorChain.pluginAll()方法:該方法在創(chuàng)建上述4個(gè)接口對(duì)象時(shí)調(diào)用,其含義為給這些接口對(duì)象注冊(cè)攔截器功能,注意是注冊(cè),而不是執(zhí)行攔截。
攔截器執(zhí)行時(shí)機(jī):plugin()方法注冊(cè)攔截器后,那么,在執(zhí)行上述4個(gè)接口對(duì)象內(nèi)的具體方法時(shí),就會(huì)自動(dòng)觸發(fā)攔截器的執(zhí)行,也就是插件的執(zhí)行。
所以,一定要分清,何時(shí)注冊(cè),何時(shí)執(zhí)行。切不可認(rèn)為pluginAll()或plugin()就是執(zhí)行,它只是注冊(cè)。
4. Invocation
public?class?Invocation?{
??private?Object?target;
??private?Method?method;
??private?Object[]?args;
}
intercept(Invocation invocation)方法的參數(shù)Invocation ,我相信你一定可以看得懂,不解釋。
5. 初始化插件源碼解析
org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode)方法部分源碼。
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?=?child.getChildrenAsProperties();
????????Interceptor?interceptorInstance?=?(Interceptor)?resolveClass(interceptor).newInstance();
????????//?這里展示了setProperties()方法的調(diào)用時(shí)機(jī)
????????interceptorInstance.setProperties(properties);
????????configuration.addInterceptor(interceptorInstance);
??????}
????}
??}
對(duì)于Mybatis,它并不區(qū)分是何種攔截器接口,所有的插件都是Interceptor,Mybatis完全依靠Annotation去標(biāo)識(shí)對(duì)誰進(jìn)行攔截,所以,具備接口一致性。
6. 分頁插件原理
由于Mybatis采用的是邏輯分頁,而非物理分頁,那么,市場(chǎng)上就出現(xiàn)了可以實(shí)現(xiàn)物理分頁的Mybatis的分頁插件。
要實(shí)現(xiàn)物理分頁,就需要對(duì)String sql進(jìn)行攔截并增強(qiáng),Mybatis通過BoundSql對(duì)象存儲(chǔ)String sql,而BoundSql則由StatementHandler對(duì)象獲取。
public?interface?StatementHandler?{
?????List?query(Statement?statement,?ResultHandler?resultHandler)?throws?SQLException ;
????BoundSql?getBoundSql();
}
public?class?BoundSql?{
???public?String?getSql()?{
????return?sql;
??}
}
因此,就需要編寫一個(gè)針對(duì)StatementHandler的query方法攔截器,然后獲取到sql,對(duì)sql進(jìn)行重寫增強(qiáng)。
任它天高海闊,任它變化無窮,我們只要懂得原理,再多插件,我們都可以對(duì)其投送王之蔑視。
推薦一個(gè)專注后端面試的公眾號(hào)
長(zhǎng)按掃碼關(guān)注,每天學(xué)習(xí),一起進(jìn)大廠!
往期推薦

