mybatis源码阅读(八) ---Interceptor了解一下
轉載自??mybatis源碼閱讀(八) ---Interceptor了解一下?
1 Intercetor
MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis允許使用插件來攔截的方法調用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 攔截執行器的方法
 ParameterHandler (getParameterObject, setParameters) 攔截參數的處理
 ResultSetHandler (handleResultSets, handleOutputParameters) 攔截結果集的處理
 StatementHandler (prepare, parameterize, batch, update, query) 攔截Sql語法構建的處理
InterceptorChain里保存了所有的攔截器,它在mybatis初始化的時候創建。上面interceptorChain.pluginAll(executor)的含義是調用攔截器鏈里的每個攔截器依次對executor進行plugin(插入攔截)代碼如下
public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();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);}}Interceptor 結構
public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);}2.自定義攔截器
官方源碼例子
@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class ExamplePlugin implements Interceptor {private Properties properties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}public Properties getProperties() {return properties;}}mybatis-config.xml配置
<plugins><plugin interceptor="org.lpf.interceptor.ExamplePlugin"></plugin> </plugins>每一個攔截器都必須實現上面的三個方法,其中:
Object intercept(Invocation invocation)是實現攔截邏輯的地方,內部要通過invocation.proceed()顯式地推進責任鏈前進,也就是調用下一個攔截器攔截目標方法。
Object plugin(Object target)就是用當前這個攔截器生成對目標target的代理,實際是通過Plugin.wrap(target,this)來完成的,把目標target和攔截器this傳給了包裝函數。
setProperties(Properties properties)用于設置額外的參數,參數配置在攔截器的Properties節點里。
注解里描述的是指定攔截方法的簽名 [type,method,args] (即對哪種對象的哪種方法進行攔截),它在攔截前用于決斷。
定義自己的Interceptor最重要的是要實現plugin方法和intercept方法,在plugin方法中我們可以決定是否要進行攔截進而決定要返回一個什么樣的目標對象。而intercept方法就是要進行攔截的時候要執行的方法。
對于plugin方法而言,其實Mybatis已經為我們提供了一個實現。Mybatis中有一個叫做Plugin的類,里面有一個靜態方法wrap(Object target,Interceptor interceptor),通過該方法可以決定要返回的對象是目標對象還是對應的代理。這里我們先來看一下Plugin的源碼:
/*** @author Clinton Begin* 這個類是Mybatis攔截器的核心,大家可以看到該類繼承了InvocationHandler* 又是JDK動態代理機制*/ public class Plugin implements InvocationHandler {//目標對象private final Object target;// 攔截器private final Interceptor interceptor;//記錄需要被攔截的類與方法 提高性能private final 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;}//一個靜態方法,對一個目標對象進行包裝,生成代理類。public static Object wrap(Object target, Interceptor interceptor) {//首先根據interceptor上面定義的注解 獲取需要攔截的信息Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//目標對象的ClassClass<?> type = target.getClass();//返回需要攔截的接口信息Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//如果長度為>0 則返回代理類 否則不做處理if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}//代理對象每次調用的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//通過method參數定義的類 去signatureMap當中查詢需要攔截的方法集合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);}}//根據攔截器接口(Interceptor)實現類上面的注解獲取相關信息private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {//獲取注解信息@InterceptsIntercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {//為空則拋出異常throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); }//獲得Signature注解信息 是一個數組Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();//循環注解信息for (Signature sig : sigs) {//根據Signature注解定義的type信息去signatureMap當中查詢需要攔截方法的集合Set<Method> methods = signatureMap.get(sig.type());if (methods == null) { //第一次肯定為null 就創建一個并放入signatureMapmethods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {//找到sig.type當中定義的方法 并加入到集合Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}//根據對象類型與signatureMap獲取接口信息private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<Class<?>>();//循環type類型的接口信息 如果該類型存在與signatureMap當中則加入到set當中去while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}//轉換為數組返回return interfaces.toArray(new Class<?>[interfaces.size()]);}}3.代理鏈上的攔截
我們再次結合(Executor)interceptorChain.pluginAll(executor)這個語句來看,這個語句內部對executor執行了多次plugin,第一次plugin后通過Plugin.wrap方法生成了第一個代理類,姑且就叫executorProxy1,這個代理類的target屬性是該executor對象。第二次plugin后通過Plugin.wrap方法生成了第二個代理類,姑且叫executorProxy2,這個代理類的target屬性是executorProxy1...這樣通過每個代理類的target屬性就構成了一個代理鏈(從最后一個executorProxyN往前查找,通過target屬性可以找到最原始的executor類)
當Configuration調用newExecutor方法的時候,由于Executor接口的update(MappedStatement ms, Object parameter)方法被攔截器被截獲。因此最終返回的是一個代理類Plugin,而不是Executor。這樣調用方法的時候,如果是個代理類,那么會執行:
//代理對象每次調用的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//通過method參數定義的類 去signatureMap當中查詢需要攔截的方法集合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);} }那么會執行Interceptor接口的interceptor方法 如下
public Object intercept(Invocation invocation) throws Throwable {return invocation.proceed(); }傳遞給攔截器的是一個Invocation對象,如下
public class Invocation {private final Object target;private final Method method;private final 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);}可以看到,Invocation類保存了代理對象的目標類,執行的目標類方法以及傳遞給它的參數。
在每個攔截器的intercept方法內,最后一個語句一定是return invocation.proceed()(不這么做的話攔截器鏈就斷了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是簡單的調用了下target的對應方法,如果target還是個代理,就又回到了上面的Plugin.invoke方法了。這樣就形成了攔截器的調用鏈推進。
4.總結
總體來說MyBatis攔截器還是很簡單的,攔截器本身不需要太多的知識點,但是學習攔截器需要對MyBatis中的各個接口很熟悉,因為攔截器涉及到了各個接口的知識點。
我們假設在MyBatis配置了一個插件,在運行時會發生什么?
如果有N個插件,就有N個代理,每個代理都要執行上面的邏輯。這里面的層層代理要多次生成動態代理,是比較影響性能的。雖然能指定插件攔截的位置,但這個是在執行方法時動態判斷,初始化的時候就是簡單的把插件包裝到了所有可以攔截的地方。
因此,在編寫插件時需注意以下幾個原則:
總結
以上是生活随笔為你收集整理的mybatis源码阅读(八) ---Interceptor了解一下的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 电脑越用越慢为什么苹果电脑越用越慢
 - 下一篇: 上阳赋剧情人物关系 上阳赋有哪些人物关系