目錄
攔截器接口
注冊攔截器
攔截原理
簡單打印的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)限攔截器
- mybatis攔截器常用于分頁器杠娱,網(wǎng)上大多代碼也是講的分頁器。我用的框架中已自帶分頁器谱煤,所以不再需要自己編寫.
- 在mybatis配置文件中注冊時(shí)發(fā)現(xiàn)摊求,原本以為分頁器最后執(zhí)行應(yīng)該最后注冊,實(shí)際上卻發(fā)現(xiàn)越后執(zhí)行的攔截器就要放在越上面刘离,這個(gè)攔截器我放在了最下面睹簇。
- 網(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();
}
}
}
}
}