1. 程式人生 > >Mybatis那些事-攔截器(Plugin+Interceptor)

Mybatis那些事-攔截器(Plugin+Interceptor)

uil number lin title 對象 cut collect chm source

作者:yhjyumi的專欄

Mybatis的攔截器實現機制,使用的是JDK的InvocationHandler.

當我們調用ParameterHandler,ResultSetHandler,StatementHandler,Executor的對象的時候,
實際上使用的是Plugin這個代理類的對象,這個類實現了InvocationHandler接口.
接下來我們就知道了,在調用上述被代理類的方法的時候,就會執行Plugin的invoke方法.
Plugin在invoke方法中根據@Intercepts的配置信息(方法名,參數等)動態判斷是否需要攔截該方法.
再然後使用需要攔截的方法Method封裝成Invocation,並調用Interceptor的proceed方法.

這樣我們就達到了攔截目標方法的結果.
例如Executor的執行大概是這樣的流程:
攔截器代理類對象->攔截器->目標方法
Executor->Plugin->Interceptor->Invocation
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke


技術分享
註解
@Intercepts 在實現Interceptor接口的類聲明,使該類註冊成為攔截器
Signature[] value//定義需要攔截哪些類,哪些方法
@Signature 定義哪些類(4種),方法,參數需要被攔截
Class<?> type()//ParameterHandler,ResultSetHandler,StatementHandler,Executor
String method()//
Class<?>[] args()//

接口
Interceptor 實現攔截器的接口


InterceptorChain 攔截器鏈,保存了Mybatis配置的所有攔截器,保存在Configuration
List<Interceptor> interceptors//攔截器

Invocation 類方法的一個封裝,在攔截器中就是被調用的目標方法
Object target//調用的對象
Method method//調用的方法
Object[] args//參數

Plugin 插件,其實就是ParameterHandler,ResultSetHandler,StatementHandler,Executor的代理類(Mybatis使用的JDK代理實現攔截器),實現了InvocationHandler接口
Object target//被代理的目標對象
Interceptor interceptor//攔截器
Map<Class<?>, Set<Method>> signatureMap//接口需要攔截的方法(一對多,每個接口對應多個方法)
//
wrap(Object target, Interceptor interceptor)//把攔截器對象封裝成Plugin代理對象.
invoke(Object proxy, Method method, Object[] args)//


使用例子
1.寫一個類,並且實現Interceptor接口
2.在上述類使用@Intercepts註解,配置攔截信息
3.在配置文件配置插件
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub

}
}
配置文件加入
<plugins>
<plugin interceptor="weber.mybatis.plugin.MyInterceptor" />
</plugins>

加入測試代碼

[java] view plain copy
  1. public static void main(String[] args) throws IOException {
  2. String resource = "conf.xml";
  3. InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
  4. SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
  5. SqlSession session = sessionFactory.openSession();
  6. // User user = session.selectOne("weber.mybatis.mapper.getUser", 1);
  7. User userObj = new User();
  8. userObj.setId(123);
  9. session.selectList("weber.mybatis.mapper.searchByUser", userObj);
  10. // UserMapper mapper = session.getMapper(UserMapper.class);
  11. // mapper.selectAll();
  12. // System.out.println(user);
  13. }

開啟debug模式,我們將看到Plugin是如何實現代理的.

當代碼執行到下圖紅色部分的時候

技術分享

將直接跳到下圖!所以,我們此時的executor不是CachingExecutor對象,而是Plugin代理對象.

技術分享

技術分享

此時的method,就是被調用的目標方法如下:

技術分享

最後附上源碼註釋

[java] view plain copy
  1. /**
  2. * @author Clinton Begin
  3. */
  4. //Plugin是JDK動態代理類
  5. public class Plugin implements InvocationHandler {
  6. private Object target;//目標對象(ParameterHandler,ResultSetHandler,StatementHandler,Executor)
  7. private Interceptor interceptor;//被代理的攔截器
  8. //目標類需要攔截的方法緩存.因為一個攔截器可以攔截多個類,一個類可以攔截多個方法.
  9. //所以用Map + Set的數據結構存儲
  10. private Map<Class<?>, Set<Method>> signatureMap;//保存每個攔截器的@signature的配置信息
  11. private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
  12. this.target = target;
  13. this.interceptor = interceptor;
  14. this.signatureMap = signatureMap;
  15. }
  16. //把目標對象和攔截器封裝成Plugin代理類實例.
  17. public static Object wrap(Object target, Interceptor interceptor) {
  18. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//獲取攔截器的攔截信息(需要攔截的類和方法)
  19. Class<?> type = target.getClass();
  20. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//Proxy代理只能代理接口
  21. if (interfaces.length > 0) {
  22. return Proxy.newProxyInstance(
  23. type.getClassLoader(),
  24. interfaces,
  25. new Plugin(target, interceptor, signatureMap));//Plugin作為代理類,但是實際業務是由Interceptor攔截器完成的.
  26. }
  27. return target;
  28. }
  29. @Override
  30. //proxy,類代理的對象,例如CachingExecutor對象
  31. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  32. try {//從這裏代碼看到,會攔截所有的Executor方法,動態的去判斷攔截器要不要去攔截.所以要小心使用攔截器,會影響性能.
  33. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  34. if (methods != null && methods.contains(method)) {
  35. //Invocation是目標對象,目標對象需要攔截的方法,我攔截方法的參數的封裝.
  36. return interceptor.intercept(new Invocation(target, method, args));//調用攔截器實現攔截
  37. }
  38. return method.invoke(target, args);//不需要攔截的方法直接放行
  39. } catch (Exception e) {
  40. throw ExceptionUtil.unwrapThrowable(e);
  41. }
  42. }
  43. private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  44. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);//獲取攔截器註解@Signature
  45. // issue #251
  46. if (interceptsAnnotation == null) {
  47. throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  48. }
  49. Signature[] sigs = interceptsAnnotation.value();//一個Signature表示一個攔截類型
  50. //保存需要攔截類的信息,class作為key, 需要攔截類的方法作為value集合Set保存.一個攔截器可以攔截一個類中多個方法
  51. Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
  52. for (Signature sig : sigs) {
  53. Set<Method> methods = signatureMap.get(sig.type());
  54. if (methods == null) {
  55. methods = new HashSet<Method>();
  56. signatureMap.put(sig.type(), methods);
  57. }
  58. try {
  59. Method method = sig.type().getMethod(sig.method(), sig.args());//獲取需要攔截的方法
  60. methods.add(method);
  61. } catch (NoSuchMethodException e) {
  62. throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  63. }
  64. }
  65. return signatureMap;
  66. }
  67. private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  68. Set<Class<?>> interfaces = new HashSet<Class<?>>();
  69. while (type != null) {
  70. for (Class<?> c : type.getInterfaces()) {
  71. if (signatureMap.containsKey(c)) {
  72. interfaces.add(c);
  73. }
  74. }
  75. type = type.getSuperclass();
  76. }
  77. return interfaces.toArray(new Class<?>[interfaces.size()]);
  78. }
  79. }

[java] view plain copy
  1. /**
  2. * @author Clinton Begin
  3. */
  4. //InterceptorChain裏保存了所有的攔截器,它在mybatis初始化的時候創建。存在Configuration中
  5. public class InterceptorChain {
  6. private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  7. //每一個攔截器對目標類都進行一次代理(也就是會出現代理的代理的代理.....有點拗口)
  8. public Object pluginAll(Object target) {
  9. for (Interceptor interceptor : interceptors) {
  10. target = interceptor.plugin(target);
  11. }
  12. return target;
  13. }
  14. public void addInterceptor(Interceptor interceptor) {
  15. interceptors.add(interceptor);
  16. }
  17. public List<Interceptor> getInterceptors() {
  18. return Collections.unmodifiableList(interceptors);
  19. }
  20. }

[java] view plain copy
    1. /**
    2. * @author Clinton Begin
    3. */
    4. public interface Interceptor {
    5. Object intercept(Invocation invocation) throws Throwable;//攔截方法,在這裏處理攔截器的業務邏輯
    6. Object plugin(Object target);//把目標對象封裝成Plugin對象
    7. void setProperties(Properties properties);
    8. }

Mybatis那些事-攔截器(Plugin+Interceptor)