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>