http://blog.csdn.net/elim168/article/details/40737009
org.apache.ibatis.plugin.Interceptor,MyBATIS的插件可以攔截Executor,StatementHandler,ParameterHandler和ResultHandler對象(下簡稱四大對象)
1,對於接口Executor,method只定義update,query,flushStatements,commit,rollback,createCacheKey,isCached,clearLocalCache,deferLoad,getTransaction,close,isClosed這幾個方法,沒有delete和insert方法(用update代替)
2,插件的運行原理:
為了方便生成代理對象和綁定方法,MyBATIS為我們提供了一個Plugin類的,我們經常需要在插件的plugin方法中調用它
這里有兩個十分重要的方法,wrap和invoke方法。
wrap方法顯然是為了生成一個動態代理類。它利用接口Interceptor綁定,用Plugin類對象代理,這樣被綁定對象調用方法時,就會進入Plugin類的invoke方法里。
invoke方法是代理綁定的方法。學習了動態代理就知道,當一個對象被wrap方法綁定就會進入到這個方法里面。
invoke方法實現的邏輯是:首先判定簽名類和方法是否存在,如果不存在則直接反射調度被攔截對象的方法,如果存在則調度插件的interceptor方法,這時候會初始化一個Invocation對象,這個對象比較簡單
3,MyBatis之簡單了解Plugin,MyBatis的Configuration配置中有一個Plugin配置,根據其名可以解釋為“插件”,在mybatis-config.xml的配置文件中注冊自定義Plugin
4,關於Interceptor接口實現:
定義自己的Interceptor最重要的是要實現plugin方法和intercept方法,在plugin方法中我們可以決定是否要進行攔截進而決定要返回一個什么樣的目標對象。而intercept方法就是要進行攔截的時候要執行的方法,setProperties方法是用於在Mybatis配置文件中指定一些屬性的, 對於plugin方法而言,其實Mybatis已經為我們提供了一個實現。Mybatis中有一個叫做Plugin的類,里面有一個靜態方法wrap(Object target,Interceptor interceptor),通過該方法可以決定要返回的對象是目標對象還是對應的代理
wrap和invoke方法:
我們先看一下Plugin的wrap方法,它根據當前的Interceptor上面的注解定義哪些接口需要攔截,然后判斷當前目標對象是否有實現對應需要攔截的接口,如果沒有則返回目標對象本身,如果有則返回一個代理對象。而這個代理對象的InvocationHandler正是一個Plugin。所以當目標對象在執行接口方法時,如果是通過代理對象執行的,則會調用對應InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接着我們來看一下該invoke方法的內容。這里invoke方法的邏輯是:如果當前執行的方法是定義好的需要攔截的方法,則把目標對象、要執行的方法以及方法參數封裝成一個Invocation對象,再把封裝好的Invocation作為參數傳遞給當前攔截器的intercept方法。如果不需要攔截,則直接調用當前的方法。Invocation中定義了定義了一個proceed方法,其邏輯就是調用當前方法,所以如果在intercept中需要繼續調用當前方法的話可以調用invocation的procced方法。
這就是Mybatis中實現Interceptor攔截的一個思想,如果用戶覺得這個思想有問題或者不能完全滿足你的要求的話可以通過實現自己的Plugin來決定什么時候需要代理什么時候需要攔截
StatementHandler prepare
要利用JDBC對數據庫進行操作就必須要有一個對應的Statement對象,Mybatis在執行Sql語句前也會產生一個包含Sql語句的Statement對象,而且對應的Sql語句是在Statement之前產生的,所以我們就可以在它成Statement之前對用來生成Statement的Sql語句下手
更改Sql語句這個看起來很簡單,而事實上來說的話就沒那么直觀,因為包括sql等其他屬性在內的多個屬性都沒有對應的方法可以直接取到,它們對外部都是封閉的,是對象的私有屬性,所以這里就需要引入反射機制來獲取或者更改對象的私有屬性的值了
@Intercepts({@Signature( type = Executor.class,method = "query", args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}) })
@Intercepts({@Signature(method="prepare",type=StatementHandler.class,args ={Connection.class})})
package cn.com.xmh.gcoin.util;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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 cn.com.xmh.gcoin.common.util.ConsistentHashingWithTable;
import cn.com.xmh.gcoin.common.util.StringUtils;
import cn.com.xmh.gcoin.entity.TblAccountTransaction;
@Intercepts({@Signature(method="prepare",type=StatementHandler.class,args ={Connection.class})})
//@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class })
// @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) }
public class TestInterceptor implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
//TblAccountTransactionMapper
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
Object obj = boundSql.getParameterObject();//.getParameterMappings()
String userNo = "";
if(sql != null && sql.contains("tbl_account_transaction")){
if(obj instanceof Map){
userNo = (String)((Map)obj).get("userNo");
userNo = StringUtils.isEmpty(userNo)?(String)((Map)obj).get("userno"):userNo;
}
if(obj instanceof TblAccountTransaction){
userNo = (String)((TblAccountTransaction)obj).getUserNo();
}
System.out.println("*******userNo******"+userNo);
String tbl = ConsistentHashingWithTable.getServer(userNo);
if(!StringUtils.isEmpty(tbl)){
sql = sql.replaceAll("tbl_account_transaction", tbl);
}
setFieldValue(boundSql,"sql",sql);
}
System.out.println("*******boundSql******"+sql);
// MappedStatement mappedStatement = (MappedStatement) invocation
// .getArgs()[0];
// String sqlId = mappedStatement.getId();
// String namespace = sqlId.substring(0, sqlId.indexOf('.'));
// Executor exe = (Executor) invocation.getTarget();
// String methodName = invocation.getMethod().getName();
//
// if (methodName.equals("query")) {
// Object parameter = invocation.getArgs()[1];
// RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
//
// }
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String dialect = properties.getProperty("dialect");
System.out.println("********** dialect:"+dialect);
}
/**
* 利用反射設置指定對象的指定屬性為指定的值
* @param obj 目標對象
* @param fieldName 目標屬性
* @param fieldValue 目標值
*/
public static void setFieldValue(Object obj, String fieldName,
String fieldValue) {
Field field = 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();
}
}
}
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) {
//這里不用做處理,子類沒有該字段可能對應的父類有,都沒有就返回null。
}
}
return field;
}
}
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="callSettersOnNulls" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<plugins>
<plugin interceptor="cn.com.xmh.gcoin.util.TestInterceptor"><!--過濾器引入-->
<property name="dialect" value="mysql" />
</plugin>
</plugins>
</configuration>
<bean id="gcoin-SessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="gcoinDataSource" />
<property name="configLocation" value="classpath:spring/mybatis-config.xml"/><!--配置文件引入-->
<property name="mapperLocations">
<list>
<value>classpath*:/props/gcoin/mapper/*.xml</value>
<value>classpath*:props/gcoin/mapper/tddlmapper/*.xml</value>
</list>
</property>
</bean>