Mybatis之plugin插件設計原理


  大多數框架,都支持插件,用戶可通過編寫插件來自行擴展功能,Mybatis也不例外。

  我們從插件配置、插件編寫、插件運行原理、插件注冊與執行攔截的時機、初始化插件、分頁插件的原理等六個方面展開闡述。

 一、插件配置

  Mybatis的插件配置在configuration內部,初始化時,會讀取這些插件,保存於Configuration對象的InterceptorChain中。

1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
3 <configuration>
4     <plugins>
5         <plugin interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
6             <property name="value" value="100" />
7         </plugin>
8     </plugins>
9 </configuration>
1 public class Configuration 
2 {
3     protected final InterceptorChain interceptorChain = new     
4     InterceptorChain();
5 }

org.apache.ibatis.plugin.InterceptorChain.java源碼。

 1 public class InterceptorChain {
 2 
 3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
 4 
 5   public Object pluginAll(Object target) {
 6     for (Interceptor interceptor : interceptors) {
 7       target = interceptor.plugin(target);
 8     }
 9     return target;
10   }
11 
12   public void addInterceptor(Interceptor interceptor) {
13     interceptors.add(interceptor);
14   }
15   
16   public List<Interceptor> getInterceptors() {
17     return Collections.unmodifiableList(interceptors);
18   }
19 
20 }

  上面的for循環代表了只要是插件,都會以責任鏈的方式逐一執行,所謂插件,其實就類似於攔截器。

二、插件的編寫

  插件必須實現org.apache.ibatis.plugin.Interceptor接口。

1 public interface Interceptor {
2   
3   Object intercept(Invocation invocation) throws Throwable;
4 
5   Object plugin(Object target);
6 
7   void setProperties(Properties properties);
8 
9 }

-intercept()方法:執行攔截內容的地方,攔截目標對象的目標方法的執行

-plugin()方法:決定是否觸發intercept()方法。  作用:包裝目標對象,包裝就是為目標對象創建一個代理對象

-setProperties()方法:給自定義的攔截器傳遞xml配置的屬性參數。將插件注冊時的property屬性設置進來

下面自定義一個攔截器:

 1 package com.atguigu.mybatis.dao;
 2 
 3 import java.util.Properties;
 4 
 5 import org.apache.ibatis.executor.statement.StatementHandler;
 6 import org.apache.ibatis.plugin.Interceptor;
 7 import org.apache.ibatis.plugin.Intercepts;
 8 import org.apache.ibatis.plugin.Invocation;
 9 import org.apache.ibatis.plugin.Plugin;
10 import org.apache.ibatis.plugin.Signature;
11 import org.apache.ibatis.reflection.MetaObject;
12 import org.apache.ibatis.reflection.SystemMetaObject;
13 
14 
15 /*
16  * 完成插件簽名:
17  *     告訴Mybatis當前插件用來攔截哪個對象的哪個方法
18  * 
19  * */
20 @Intercepts(
21         {
22             @Signature(type=StatementHandler.class,method ="parameterize",args = java.sql.Statement.class )
23         }
24         )
25 public class MyFirstPlugin implements Interceptor
26 {
27     /*
28      * 攔截目標對象的目標方法的執行
29      * */
30     @Override
31     public Object intercept(Invocation invocation) throws Throwable 
32     {
33         System.out.println("MyFirstPlugin的intercept方法執行,攔截方法:"+invocation.getMethod());
34         
35         //動態改變一下sql的參數,查3號員工,這里偷梁換柱一下查詢4號員工
36         Object target = invocation.getTarget();
37         System.out.println("當前攔截的對象:"+target);
38         //拿到:StatementHandler==>ParameterHandler==>parameterObject
39         //拿到target(攔截對象)的元數據
40         MetaObject metaObject = SystemMetaObject.forObject(target);
41         Object value = metaObject.getValue("parameterHandler.parameterObject");
42         System.out.println("sql語句用的參數是:"+value);
43         metaObject.setValue("parameterHandler.parameterObject", 4);
44         
45         //執行目標方法
46         Object proceed = invocation.proceed();
47         
48         
49         
50         //返回執行后的返回值
51         return proceed;
52     }
53 
54     
55     /*
56      * 插件:
57      * 作用:包裝目標對象,包裝就是為目標對象創建一個代理對象
58      * */
59     @Override
60     public Object plugin(Object target) 
61     {
62         System.out.println("MyFirstPlugin的plugin方法執行,包裝的對象(目標對象):"+target);
63         
64         
65         //我們可以借助Plugin的wrap方法來使用當前的攔截器包裝我們的目標對象
66         Object wrap = Plugin.wrap(target, this);
67         
68         //返回的就是為當前target創建的動態代理
69         return wrap;
70     }
71 
72     /*
73      * setProperties:
74      *     將插件注冊時的property屬性設置進來
75      * 
76      * */
77     @Override
78     public void setProperties(Properties properties) 
79     {
80         System.out.println("插件配置的信息:"+properties);
81     }
82 
83 }

2.1、為什么要寫Annotation注解?注解都是什么含義?

  Mybatis規定插件必須編寫Annotation注解,是必須,而不是可選。@Intercepts注解:裝載一個@Signature列表,一個@Signature其實就是一個需要攔截的方法封裝。那么,一個攔截器要攔截多個方法,自然就是一個@Signature列表。

  type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }  

  解釋:要攔截Executor接口內的query()方法,參數類型為args列表。

2.2、Plugin.wrap(target, this)是干什么的?

  使用JDK的動態代理,給target對象創建一個delegate代理對象,以此來實現方法攔截和增強功能,它會回調intercept()方法。

2.3、Mybatis可以攔截哪些接口對象?

  Mybatis只能攔截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4個接口對象內的方法。

  重新審視interceptorChain.pluginAll()方法:該方法在創建上述4個接口對象時調用,其含義為給這些接口對象注冊攔截器功能,注意是注冊,而不是執行攔截。

  攔截器執行時機:plugin()方法注冊攔截器后,那么,在執行上述4個接口對象內的具體方法時,就會自動觸發攔截器的執行,也就是插件的執行。

2.4、Invocation

1 public class Invocation {
2   private Object target;
3   private Method method;
4   private Object[] args;
5 }

  可以通過invocation來獲取攔截的目標方法,以及執行目標方法。

2.5、分頁插件原理

  由於Mybatis采用的是邏輯分頁,而非物理分頁,那么,市場上就出現了可以實現物理分頁的Mybatis的分頁插件。  要實現物理分頁,就需要對String sql進行攔截並增強,Mybatis通過BoundSql對象存儲String sql,而BoundSql則由StatementHandler對象獲取。

1 public interface StatementHandler {
2     <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
3 
4     BoundSql getBoundSql();
5 }
1 public class BoundSql {
2    public String getSql() {
3     return sql;
4   }
5 }

  因此,就需要編寫一個針對StatementHandler的query方法攔截器,然后獲取到sql,對sql進行重寫增強。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM