mybatis攔截器及項(xiàng)目應(yīng)用

目錄

攔截器接口
注冊攔截器
攔截原理
簡單打印的demo
項(xiàng)目中編寫的權(quán)限攔截器


攔截器接口

接口中的方法

Mybatis為我們提供了一個(gè)Interceptor接口徐裸,通過實(shí)現(xiàn)該接口就可以定義我們自己的攔截器遣鼓。我們先來看一下這個(gè)接口的定義:

import java.util.Properties;
 
public interface Interceptor {
  //當(dāng)plugin函數(shù)返回代理,就可以對其中的方法進(jìn)行攔截來調(diào)用intercept方法
  Object intercept(Invocation invocation) throws Throwable;
  //plugin方法是攔截器用于封裝目標(biāo)對象的重贺,通過該方法我們可以返回目標(biāo)對象本身隙疚,也可以返回一個(gè)它的代理剂买。
  Object plugin(Object target);
 //在Mybatis配置文件中指定一些屬性
  void setProperties(Properties properties);
 
}

plugin方法中我們可以決定是否要進(jìn)行攔截端辱。
intercept方法就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法醋火。

Mybatis中SqlSession下的四大核心組件:ParameterHandler 、ResultSetHandler 潜圃、StatementHandler 缸棵、Executor 。Mapper執(zhí)行的過程也是這四個(gè)組件來完成的谭期。他們包含的方法如下:
Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler
(getParameterObject, setParameters)
StatementHandler
(prepare, parameterize, batch, update, query)
ResultSetHandler
(handleResultSets, handleOutputParameters)

plugin方法參數(shù)可以接收到 這四個(gè)核心組件堵第,通常攔截StatementHandler 吧凉、Executor。
攔截 return Plugin.wrap(target, this);
不攔截 return target;

intercept方法
最后要加return invocation.proceed();
繼續(xù)執(zhí)行

實(shí)現(xiàn)接口的類的重要注解

@Intercepts用于表明當(dāng)前的對象是一個(gè)Interceptor踏志,
而@Signature則表明要攔截的接口阀捅、方法以及對應(yīng)的參數(shù)類型。

@Intercepts( {       
@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }),      
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })

第一個(gè)@Signature我們定義了該Interceptor將攔截Executor接口中參數(shù)類型為MappedStatement针余、Object饲鄙、RowBounds和ResultHandler的query方法;
第二個(gè)@Signature我們定義了該Interceptor將攔截StatementHandler中參數(shù)類型為Connection的prepare方法圆雁。

注冊攔截器

mybatis配置文件中

<configuration>
    <plugins>
       <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
           <property name="prop1" value="prop1"/>
       </plugin>
    </plugins>

攔截原理

mybatis執(zhí)行sql過程 產(chǎn)生sql語句->產(chǎn)生statement->執(zhí)行sql語句

在產(chǎn)生statement過程中可以攔截忍级。
由于Statement語句是通過RoutingStatementHandler對象的prepare方法生成的。所以摸柄,攔截StatementHandler接口的prepare方法就可以更改sql語句颤练。因?yàn)榘╯ql等其他屬性在內(nèi)的多個(gè)屬性對外部都是封閉的既忆,是對象的私有屬性驱负,所以要引入反射機(jī)制來獲取或者更改對象的私有屬性。

sqlsession四大接口對象介紹

Executor(接口)
它是一個(gè)執(zhí)行器患雇,真正進(jìn)行java與數(shù)據(jù)庫交互的對象跃脊,實(shí)際干活的。
StatementHandler(接口)
它是語句處理器苛吱,處理數(shù)據(jù)庫會(huì)話的酪术。
ParameterHandler:
它是對預(yù)編譯語句進(jìn)行參數(shù)的設(shè)置,完成對預(yù)編譯參數(shù)的設(shè)置翠储。
ResultSetHandler
返回結(jié)果绘雁,改變率很低。

四大對象的調(diào)用關(guān)系

Executor先調(diào)用StatementHandler里prepa方法預(yù)編譯SQL語句援所,并設(shè)置參數(shù)庐舟,然后再用parameterize方法來使用ParameterHandler設(shè)置參數(shù),完成預(yù)編譯住拭,執(zhí)行查詢的話挪略,使用ResultHandler將結(jié)果返回給調(diào)用者,其他操作也類似滔岳。

簡單打印的demo

import java.sql.Connection;
import java.util.Properties;
 
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
 
@Intercepts( {
       @Signature(method = "query", type = Executor.class, args = {
              MappedStatement.class, Object.class, RowBounds.class,
              ResultHandler.class }),
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {
 
    public Object intercept(Invocation invocation) throws Throwable {
       Object result = invocation.proceed();
       System.out.println("Invocation.proceed()");
       return result;
    }
 
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }
 
    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }
 
}

//來源:https://blog.csdn.net/moshenglv/article/details/52699976

mybatis配置文件中注冊后
即可在每次運(yùn)行查詢時(shí)觀察到打印語句

項(xiàng)目中編寫的權(quán)限攔截器

  1. mybatis攔截器常用于分頁器杠娱,網(wǎng)上大多代碼也是講的分頁器。我用的框架中已自帶分頁器谱煤,所以不再需要自己編寫.
  2. 在mybatis配置文件中注冊時(shí)發(fā)現(xiàn)摊求,原本以為分頁器最后執(zhí)行應(yīng)該最后注冊,實(shí)際上卻發(fā)現(xiàn)越后執(zhí)行的攔截器就要放在越上面刘离,這個(gè)攔截器我放在了最下面睹簇。
  3. 網(wǎng)上常說的攔截RoutingStatementHandler 奏赘,不知道為何,分頁器和此攔截器有一個(gè)攔截RoutingStatementHandler 后太惠,另一個(gè)就攔截不到了磨淌,因此此攔截器直接攔截的StatementHandler。

此攔截器根據(jù)查詢參數(shù)的Map中是否包含 permission參數(shù) 來決定是否攔截select函數(shù)凿渊,從而進(jìn)行頁面展示的攔截梁只。

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.loushang.bsp.security.author.intercept.web.FilterInvocation;
import org.loushang.bsp.security.session.ISessionStore;
import org.loushang.bsp.security.session.SessionStoreFactory;
import org.loushang.bsp.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;

@Intercepts({@org.apache.ibatis.plugin.Signature(type=org.apache.ibatis.executor.statement.StatementHandler.class, method="prepare", args={Connection.class})})
public class WebSqlInterceptor implements Interceptor {
 
    
    public Object intercept(Invocation invocation) throws Throwable {
        
         if(invocation.getTarget() instanceof StatementHandler) {
             StatementHandler delegate = (StatementHandler)invocation.getTarget();
              BoundSql boundSql = delegate.getBoundSql();
              Object obj = boundSql.getParameterObject();
//              if (obj instanceof Permission<?>) {
//                Permission<?> per = (Permission<?>) obj;
////                  
////                  MetaObject metaStatementHandler = SystemMetaObject.forObject(delegate);
////                  // 分離代理對象鏈(由于目標(biāo)類可能被多個(gè)插件攔截,從而形成多次代理埃脏,通過下面的兩次循環(huán)
////                  // 可以分離出最原始的的目標(biāo)類)
////                  while (metaStatementHandler.hasGetter("h")) {
////                      Object object = metaStatementHandler.getValue("h");
////                      metaStatementHandler = SystemMetaObject.forObject(object);
////                  }
////                  // 分離最后一個(gè)代理對象的目標(biāo)類
////                  while (metaStatementHandler.hasGetter("target")) {
////                      Object object = metaStatementHandler.getValue("target");
////                      metaStatementHandler = SystemMetaObject.forObject(object);
////                  }
////                  MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
////                  boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
//                  
//                MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
//                  //攔截到的prepare方法參數(shù)是一個(gè)Connection對象
//                  Connection connection = (Connection)invocation.getArgs()[0];
//                  //獲取當(dāng)前要執(zhí)行的Sql語句搪锣,也就是我們直接在Mapper映射語句中寫的Sql語句
//                  String sql = boundSql.getSql();
//                  System.out.println("成功攔截Permission sql:"+sql);
//                  String filterWebSql=null;
//                  if("web"==per.getPermissionType()) {
//                    filterWebSql =permissionGetWebSql(sql,per) ;
//                  }else if("task"==per.getPermissionType()){
//                    filterWebSql =permissionGetTaskSql(sql,per) ;
//                  }else {
//                    filterWebSql = sql;
//                  }
//                  //利用反射設(shè)置當(dāng)前BoundSql對應(yīng)的sql屬性為我們建立好的分頁Sql語句
////                  ReflectUtil.setFieldValue(boundSql, "sql", filterWebSql);
//              }else {
                  
                  MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
                  //攔截到的prepare方法參數(shù)是一個(gè)Connection對象
                  String sql = boundSql.getSql();
                      Connection connection = (Connection)invocation.getArgs()[0];
//                    System.out.println("成功攔截select sql:"+sql); 
                      Object parameterObject = boundSql.getParameterObject();
                      if (((parameterObject instanceof Map)) && 
                        (((Map)parameterObject).containsKey("permission"))) {
                          Map paraMap = (Map)parameterObject;
//                        System.out.println("成功攔截參數(shù)Map包含permission的 且value:"+paraMap.get("permission"));
                          if ((paraMap.get("permission") instanceof String)) {
                              String filterWebSql=sql;
                              String userId = (String) paraMap.get("userId");
                              StringBuffer webcondition = new StringBuffer().append("WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\" and ROLE_TYPE=\"1\")");
                              StringBuffer taskcondition = new StringBuffer().append("tab.TASK_ID in (select TASK_ID from pub_user_taskrole WHERE USER_ID=\"").append(userId).append("\" and ROLE_TYPE=\"1\")");
                              if("web"==paraMap.get("permission")&&(!"SUPERADMIN".equals(userId))) {
                                  System.out.println("攔截器過濾permission為web的sql");
                                  StringBuffer newsql = new StringBuffer();
                                  newsql.append("select tab.* from(").append(sql).append(") tab where ").append(webcondition);
                                  filterWebSql = new String(newsql);
                              }
                              else if("task"==paraMap.get("permission")&&(!"SUPERADMIN".equals(userId))){
                                  System.out.println("攔截器過濾permission為task的sql");
                                  StringBuffer newsql = new StringBuffer();
                                  /**
                                   * 若存在只有taskId沒有wensiteId的查詢 之后要編寫根據(jù)taskId查詢websiteId的語句。
                                   */
                                  //下面的是任務(wù)權(quán)限與網(wǎng)站權(quán)限混合
                                  //newsql.append("select tab.* from(").append(sql).append(") tab left join crawler_task tab on tab1.TASK_ID = tab2.TASK_ID where ").append(webcondition).append(" and ").append(taskcondition);
                                  newsql.append("select tab.* from(").append(sql).append(") tab where ").append(taskcondition);
                                  filterWebSql = new String(newsql);
                              }
                              //利用反射設(shè)置當(dāng)前BoundSql對應(yīng)的sql屬性為我們建立好的分頁Sql語句
                              ReflectUtil.setFieldValue(boundSql, "sql", filterWebSql);
                            }
                      }
              }
       return invocation.proceed();
    }
 
    //由于分頁攔截器只分頁傳入為Map類型的 傳入對象會(huì)使分頁器失效 所以放棄使用Permission 以下兩個(gè)函數(shù)均未用到
    private String permissionGetWebSql(String sql, Permission<?> per) {
        // TODO Auto-generated method stub
        String userId=per.getUserId();
        StringBuffer newsql = new StringBuffer();
        newsql.append("select * from(").append(sql).append(") tab where WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\"and ROLE_TYPE=\"1\")");
        return new String(newsql);
    }

    private String permissionGetTaskSql(String sql, Permission<?> per) {
        String userId=per.getUserId();
        StringBuffer newsql = new StringBuffer();
//      newsql.append("select * from(").append(sql).append(") tab where WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\"and ROLE_TYPE=\"1\")");
        return sql;
    }

    public Object plugin(Object target) {
        if(target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }else {
            return target;
        }
    }
 
    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }
    
    
    /**
     * 利用反射進(jìn)行操作的一個(gè)工具類
     *
     */
    private static class ReflectUtil {
       /**
        * 利用反射獲取指定對象的指定屬性
        * @param obj 目標(biāo)對象
        * @param fieldName 目標(biāo)屬性
        * @return 目標(biāo)屬性的值
        */
       public static Object getFieldValue(Object obj, String fieldName) {
           Object result = null;
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              field.setAccessible(true);
              try {
                  result = field.get(obj);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
           return result;
       }
      
       /**
        * 利用反射獲取指定對象里面的指定屬性
        * @param obj 目標(biāo)對象
        * @param fieldName 目標(biāo)屬性
        * @return 目標(biāo)字段
        */
       private static Field getField(Object obj, String fieldName) {
           Field field = null;
          for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
              try {
                  field = clazz.getDeclaredField(fieldName);
                  break;
              } catch (NoSuchFieldException e) {
                  //這里不用做處理彩掐,子類沒有該字段可能對應(yīng)的父類有构舟,都沒有就返回null。
              }
           }
           return field;
       }
 
       /**
        * 利用反射設(shè)置指定對象的指定屬性為指定的值
        * @param obj 目標(biāo)對象
        * @param fieldName 目標(biāo)屬性
         * @param fieldValue 目標(biāo)值
        */
       public static void setFieldValue(Object obj, String fieldName,
              String fieldValue) {
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              try {
                  field.setAccessible(true);
                  field.set(obj, fieldValue);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
        }
    }
 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堵幽,一起剝皮案震驚了整個(gè)濱河市狗超,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朴下,老刑警劉巖努咐,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殴胧,居然都是意外死亡渗稍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門团滥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竿屹,“玉大人,你說我怎么就攤上這事灸姊」叭迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵厨钻,是天一觀的道長扼雏。 經(jīng)常有香客問我,道長夯膀,這世上最難降的妖魔是什么诗充? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮诱建,結(jié)果婚禮上蝴蜓,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好茎匠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布格仲。 她就那樣靜靜地躺著,像睡著了一般诵冒。 火紅的嫁衣襯著肌膚如雪凯肋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天汽馋,我揣著相機(jī)與錄音侮东,去河邊找鬼。 笑死豹芯,一個(gè)胖子當(dāng)著我的面吹牛悄雅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铁蹈,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼宽闲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了握牧?” 一聲冷哼從身側(cè)響起容诬,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎我碟,沒想到半個(gè)月后放案,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姚建,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矫俺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掸冤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厘托。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖稿湿,靈堂內(nèi)的尸體忽然破棺而出铅匹,到底是詐尸還是另有隱情,我是刑警寧澤饺藤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布包斑,位于F島的核電站,受9級特大地震影響涕俗,放射性物質(zhì)發(fā)生泄漏罗丰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一再姑、第九天 我趴在偏房一處隱蔽的房頂上張望萌抵。 院中可真熱鬧,春花似錦、人聲如沸绍填。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讨永。三九已至滔驶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卿闹,已是汗流浹背瓜浸。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留比原,地道東北人插佛。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像量窘,于是被迫代替她去往敵國和親雇寇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容