-
- 一、MyBatis攔截器原理探究
- 1.1 MyBatis攔截器介紹
- 1.2 攔截器的使用
- 二、Plugin原理
- 2.1 Plugin原理
- 2.1.1 InterceptorChain
- 2.1.2 插件鍊的建立
- 2.1.3 插件攔截
- 2.1.3 Plugin
- 2.1.4 插件配置
- 2.1.5 插件實作
- 2.1 Plugin原理
- 一、MyBatis攔截器原理探究
一、MyBatis攔截器原理探究
1.1 MyBatis攔截器介紹
MyBatis提供了一種插件(plugin)的功能,雖然叫做插件,但其實這是攔截器功能。那麼攔截器攔截MyBatis中的哪些内容呢?
MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。
預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
我們看到了可以攔截Executor接口的部分方法,比如update,query,commit,rollback等方法,還有其他接口的一些方法等。
總體概括為:
- 攔截執行器的方法
- 攔截參數的處理
- 攔截結果集的處理
- 攔截Sql文法建構的處理
1.2 攔截器的使用
首先我們看下MyBatis攔截器的接口定義:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
比較簡單,隻有3個方法。 MyBatis預設沒有一個攔截器接口的實作類,開發者們可以實作符合自己需求的攔截器。
下面的MyBatis官網的一個攔截器執行個體:
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
全局xml配置:
<plugins>
<plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>
這個攔截器攔截Executor接口的update方法(其實也就是SqlSession的新增,删除,修改操作),所有執行executor的update方法都會被該攔截器攔截到。
二、Plugin原理
2.1 Plugin原理
Plugin的實作采用了Java的動态代理,應用了責任鍊設計模式
2.1.1 InterceptorChain
攔截器鍊,用于儲存從配置檔案解析後的所有攔截器
2.1.2 插件鍊的建立
在Configuration解析配置檔案的時候,XMLConfigBuilder.parseConfiguration中會調用pluginElement解析插件資訊并執行個體化後,儲存到插件鍊中
// /configuration/plugins節點
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 擷取所有的插件定義
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 反射,執行個體化插件
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 儲存到插件鍊中
configuration.addInterceptor(interceptorInstance);
}
}
}
// Configuration.addInterceptor
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
// 所有攔截器執行個體
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
2.1.3 插件攔截
在MyBatis中,隻能攔截四種接口的實作類:
- Executor
- ParameterHandler
- ResultSetHandler
-
StatementHandler
每種類型的攔截方式都是一樣的,這裡取executor為例:
在建立SqlSession的時候,會需要建立Executor實作類,在建立時,會調用插件鍊的加載插件功能:executor = (Executor) interceptorChain.pluginAll(executor);,該方法會形成一個調用鍊。
// 依次調用每個插件的plugin方法,如果該插件無需攔截target,則直接傳回target
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
2.1.3 Plugin
插件代理的實作,這裡應用了Java Dynamic Proxy
public class Plugin implements InvocationHandler {
// 需要被代理的執行個體
private Object target;
// 攔截器執行個體
private Interceptor interceptor;
// 攔截器需要攔截的方法摘要,這裡Class鍵為Executor等上述的四個
// 值為需要被攔截的方法
private Map<Class<?>, Set<Method>> signatureMap;
// 此類不能直接建立,需要通過靜态方法wrap來建立代理類
private Plugin(Object target, Interceptor interceptor,
Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 擷取需要被代理類的所有待攔截的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > ) {
// 建立代理類
return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 沒有需要攔截的方法,直接傳回原執行個體
return target;
}
// 在代理類中調用
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 判斷是否為待攔截方法,這裡為動态判斷,所有在攔截器多的時候,會影響性能
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method,
args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
// 擷取需要被攔截的方法摘要
private static Map<Class<?>, Set<Method>> getSignatureMap(
Interceptor interceptor) {
// 先擷取攔截器實作類上的注解,提取需要被攔截的方法
/* 注解示例:@Intercepts(value={@Signature(args={Void.class},method="query",type=Void.class)})*/
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(
Intercepts.class);
if (interceptsAnnotation == null) { // issue #251
throw new PluginException(
"No @Intercepts annotation was found in interceptor "
+ interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
// 根據方法名以及參數擷取待攔截方法
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on "
+ sig.type() + " named " + sig.method() + ". Cause: "
+ e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type,
Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
2.1.4 插件配置
在mybatis.xml配置檔案:
<plugins>
<plugin interceptor="com.shareinfo.framework.pagination.mybatis.PageInterceptor" />
</plugins>
2.1.5 插件實作
// Ibatis 分頁攔截器
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class PageInterceptor implements Interceptor
{
static int MAPPED_STATEMENT_INDEX = ;
static int PARAMETER_INDEX = ;
static int ROWBOUNDS_INDEX = ;
static int RESULT_HANDLER_INDEX = ;
public Object intercept(Invocation invocation) throws Throwable
{
processIntercept(invocation.getArgs());
return invocation.proceed();
}
public void processIntercept(Object[] queryArgs) throws ConfigurationException
{
// 目前環境 MappedStatement,BoundSql,及sql取得
MappedStatement mappedStatement = (MappedStatement)queryArgs[MAPPED_STATEMENT_INDEX];
// 請求的對象
Object parameter = queryArgs[PARAMETER_INDEX];
// 分頁資訊
RowBounds rowBounds = (RowBounds)queryArgs[ROWBOUNDS_INDEX];
int offset = rowBounds.getOffset();
int limit = rowBounds.getLimit();
if (offset != || limit != Integer.MAX_VALUE)
{
Dialect dialect = getDialect(mappedStatement.getConfiguration());
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
String sql = boundSql.getSql().trim();
sql = dialect.getPaginationSql(sql, offset, limit);
offset = ; // 這裡沒有增加的話導緻後面分頁查詢不出來
limit = Integer.MAX_VALUE;
queryArgs[ROWBOUNDS_INDEX] = new RowBounds(offset, limit);
BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
}
}
private Dialect getDialect(Configuration configuration) throws ConfigurationException
{
Dialect.Type databaseType = null;
try
{
databaseType = Dialect.Type.valueOf(configuration.getVariables().getProperty("dialect").toUpperCase());
}
catch (Exception e)
{
throw new ConfigurationException("the value of the dialect property in mybatis-config.xml is not defined : " + configuration.getVariables().getProperty("dialect"));
}
Dialect dialect = null;
switch (databaseType)
{
case MYSQL:
dialect = new MySQL5Dialect();
case SQLSERVER:
dialect = new SqlServerDialect();
case ORACLE:
dialect = new OracleDialect();
}
return dialect;
}
private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource)
{
Builder builder = new Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
// builder.keyProperty(ms.getKeyProperty());
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
public class BoundSqlSqlSource implements SqlSource
{
BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql)
{
this.boundSql = boundSql;
}
public BoundSql getBoundSql(Object parameterObject)
{
return boundSql;
}
}
public Object plugin(Object arg0)
{
return Plugin.wrap(arg0, this);
}
public void setProperties(Properties properties)
{
}
}