Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理
文章目錄
- 前言
- InterceptorChain保存所有的Interceptor
- 創建四大對象都走Configuration
- InterceptorChain增強對象方法
- Plugin封裝動態代理,讓你使用Mybatis攔截器更簡單
- Invocation,讓我們能在攔截器中使用動態代理類中的invoke方法中的對象
- 調用時序圖
- 小結
前言
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);}}}//加入到interceptorChainpublic 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中的對象。
總結
以上是生活随笔為你收集整理的Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python精要(72)-函数参数列表副
- 下一篇: java多线程之ThreadLoal详解