前言
mybatis攔截器是一個非常有用的功能,當你想實現自動分頁,自動記錄執行的sql等功能時,若在service層,每次調用時,都寫代碼的話,會非常麻煩,而使用mybatis攔截器,就可以非常輕鬆的實現了。
Executor , ResultSetHandler,StatementHandler,ParameterHandler,這是Mybatis中的四大對象,也是攔截器的切入點。我們可以基於這四大對象的方法進行增強。解釋一下,因爲這四個都是接口,我們可以利用動態代理進行方法的增強。
動態代理,這是底層的原理,若只用了動態代理,那麼你肯定要自己寫代理類,在使用的時候實例化,然後再替換Mybatis裏面的原有對象,對不?但是實際你並不需要這麼做,那是因爲mybatis一開始就爲你設計好了讓你如何簡單快速的添加攔截器,讓你在添加攔截器的時候只用關注業務邏輯,而不需要管類之間的關係。
那麼Mybatis究竟是怎麼做到這一點的呢?接下來就讓LZ帶大家來看看Mybatis究竟是如何實現的吧。
我們實現mybatis攔截器的步驟,首先創建Interceptor的實現類,然後我們要在mybatis.xml中配置plugins,這就是我們爲Mybatis添加攔截器的步驟。
InterceptorChain保存所有的Interceptor
我們來到Configuration類中,看到裏面他有個屬性叫InterceptorChain,裏面是用來存放我們的所有攔截器,針對四大對象的攔截器全部在裏面。
//這裏就是解析並把plugin加入到interceptorChain中
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();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
//加入到interceptorChain
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
創建四大對象都走Configuration
然後,Mybatis在創建四大對象的時候都是走的Configuration類中的方法
//創建ParameterHandler對象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//都有interceptorChain.pluginAll()
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//創建ResultSetHandler對象
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);
//都有interceptorChain.pluginAll()
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//創建StatementHandler對象
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);
//都有interceptorChain.pluginAll()
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
//創建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);
}
//都有interceptorChain.pluginAll()
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
觀察上面的四個函數,他們都有一個共有的點,調用了interceptorChain.pluginAll()方法,這也是Mybatis實現攔截器功能的中點,這個pluginAll(),使攔截器有了一個特性,那就是邏輯可以向下傳遞,是責任鏈模式。
InterceptorChain增強對象方法
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
//通過for循環遍歷interceptors,將所有的interceptor都加進去。
//一層包一層,直到所有的interceptor都包裝好
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);
}
}
這裏進去的對象,和出來的對象已經不是同一個了。進去的基礎四大對象,出來的是增強版四大對象。
這裏其實用到了責任鏈模式,每個Interceptor 都有自己要服務的對象,只有當請求的方法和Interceptor要服務的對象匹配時,它纔會執行,你攔截器裏的方法。
接下來LZ帶大家來看看Mybatis是怎麼比對兩個對象是否匹配的。
Plugin封裝動態代理,讓你使用Mybatis攔截器更簡單
我們去Plugin類中
//看到這個類,大家有沒有覺得很熟悉,沒錯,實現了InvocationHandler ,使用動態代理
//這個就是Mybatis實現攔截器功能的底層。
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
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;
}
//我們不需要手動的實例化動態代理對象,是因爲wrap爲我們做了這件事
public static Object wrap(Object target, Interceptor interceptor) {
//這句也蠻重要的,是將我們寫的攔截器類的註解轉換成了map,key爲我們的類對象,value是方法
//爲了之後判斷是否需要執行攔截器的方法
//這句就不進去仔細分析了,因爲很簡單,就是解析註解而已
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//這裏實例了動態代理對象
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//獲得方法
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
//如果方法不爲空,說明這個對象確實有拓展攔截器,之後看method是不是有這個方法,有的話才說明這個方法確實被拓展了,之後執行interceptor.intercept()即調用攔截器的方法
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
Invocation,讓我們能在攔截器中使用動態代理類中的invoke方法中的對象
//通過這個對象,把代理類中的invoke方法中的對象和我們攔截器類相連。
//我們在攔截器類中的intercept(Invocation invocation)中的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);
}
}
調用時序圖
小結
總結一下,其實底層用到的還是動態代理,但是Mybatis通過封裝,讓我們開發攔截器更加簡單。通過InterceptorChain,使得攔截器能將邏輯向下傳遞,然後通過Invocation,讓攔截器類能使用到動態代理類invoke中的對象。