Mybatis插件原理(拦截器)

    • 一、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 插件实现

一、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 interceptors = new ArrayList();

  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 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, Set> signatureMap;

    // 此类不能直接创建,需要通过静态方法wrap来创建代理类
    private Plugin(Object target, Interceptor interceptor,
            Map, Set> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        Map, Set> signatureMap = getSignatureMap(interceptor);
        Class type = target.getClass();
        // 获取需要被代理类的所有待拦截的接口
        Class[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // 创建代理类
            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 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, Set> 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, Set> signatureMap = new HashMap, Set>();
        for (Signature sig : sigs) {
            Set methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet();
                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, Set> signatureMap) {
        Set> interfaces = new HashSet>();
        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 = 0;
    static int PARAMETER_INDEX = 1;
    static int ROWBOUNDS_INDEX = 2;
    static int RESULT_HANDLER_INDEX = 3;

    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 != 0 || 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 = 0; // 这里没有增加的话导致后面分页查询不出来
            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)
    {
    }
}

你可能感兴趣的:(MyBatis/Ibatis)