精华内容
下载资源
问答
  • Mybatis拦截器

    万次阅读 多人点赞 2019-04-16 22:31:18
    Mybatis拦截器介绍        Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择...

    一 Mybatis拦截器介绍

           Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。所以Mybatis拦截器的使用范围是非常广泛的。

           Mybatis里面的核心对象还是比较多,如下:

    Mybatis核心对象 解释
    SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
    Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
    StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
    ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
    ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
    TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
    MappedStatement MappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装
    SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
    BoundSql 表示动态生成的SQL语句以及相应的参数信息
    Configuration MyBatis所有的配置信息都维持在Configuration对象之中

           Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个对象里面的方法。

    • Executor

           Mybatis中所有的Mapper语句的执行都是通过Executor进行的。Executor是Mybatis的核心接口。从其定义的接口方法我们可以看出,对应的增删改语句是通过Executor接口的update方法进行的,查询是通过query方法进行的。Executor里面常用拦截方法如下所示。

    public interface Executor {
    
       ...
    
        /**
         * 执行update/insert/delete
         */
        int update(MappedStatement ms, Object parameter) throws SQLException;
    
        /**
         * 执行查询,先在缓存里面查找
         */
        <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    
        /**
         * 执行查询
         */
        <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    
        /**
         * 执行查询,查询结果放在Cursor里面
         */
        <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
    
        ...
    
    
    }
    
    • ParameterHandler

           ParameterHandler用来设置参数规则,当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数。所以如果有对参数做自定义逻辑处理的时候,可以通过拦截ParameterHandler来实现。ParameterHandler里面可以拦截的方法解释如下:

    public interface ParameterHandler {
    
     ...
    
     /**
      * 设置参数规则的时候调用 -- PreparedStatement
      */
     void setParameters(PreparedStatement ps) throws SQLException;
    
     ...
    
    
    }
    
    • StatementHandler

           StatementHandler负责处理Mybatis与JDBC之间Statement的交互。

    public interface StatementHandler {
    
        ...
    
        /**
         * 从连接中获取一个Statement
         */
        Statement prepare(Connection connection, Integer transactionTimeout)
                throws SQLException;
    
        /**
         * 设置statement执行里所需的参数
         */
        void parameterize(Statement statement)
                throws SQLException;
    
        /**
         * 批量
         */
        void batch(Statement statement)
                throws SQLException;
    
        /**
         * 更新:update/insert/delete语句
         */
        int update(Statement statement)
                throws SQLException;
    
        /**
         * 执行查询
         */
        <E> List<E> query(Statement statement, ResultHandler resultHandler)
                throws SQLException;
    
        <E> Cursor<E> queryCursor(Statement statement)
                throws SQLException;
    
        ...
    
    }
    

    一般只拦截StatementHandler里面的prepare方法。

           在Mybatis里面RoutingStatementHandler是SimpleStatementHandler(对应Statement)、PreparedStatementHandler(对应PreparedStatement)、CallableStatementHandler(对应CallableStatement)的路由类,所有需要拦截StatementHandler里面的方法的时候,对RoutingStatementHandler做拦截处理就可以了,如下的写法可以过滤掉一些不必要的拦截类。

    @Intercepts({
            @Signature(
                    type = StatementHandler.class,
                    method = "prepare",
                    args = {Connection.class, Integer.class}
            )
    })
    public class TableShardInterceptor implements Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof RoutingStatementHandler) {
                // TODO: 做自己的逻辑
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
            return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    

    关于Statement、PreparedStatement和CallableStatement的一些区别。以及Statement和PreparedStatement相比PreparedStatement的优势在哪里。强烈建议大家去百度下。

    • ResultSetHandler

           ResultSetHandler用于对查询到的结果做处理。所以如果你有需求需要对返回结果做特殊处理的情况下可以去拦截ResultSetHandler的处理。ResultSetHandler里面常用拦截方法如下:

    public interface ResultSetHandler {
    
        /**
         * 将Statement执行后产生的结果集(可能有多个结果集)映射为结果列表
         */
        <E> List<E> handleResultSets(Statement stmt) throws SQLException;
        <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    
        /**
         * 处理存储过程执行后的输出参数
         */
        void handleOutputParameters(CallableStatement cs) throws SQLException;
    
    }
    

    二 Mybatis拦截器的使用

           Mybatis拦截器的使用,分两步:自定义拦截器类、注册拦截器类。

    2.1 自定义拦截器类

           自定义的拦截器需要实现Interceptor接口,并且需要在自定义拦截器类上添加@Intercepts注解。

    2.1.1 Interceptor接口

           Interceptor接口里面就三个方法。如下所示:

    public interface Interceptor {
    
        /**
         * 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
         */
        Object intercept(Invocation invocation) throws Throwable;
    
        /**
         * plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
         *
         * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
         * 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
         */
        Object plugin(Object target);
    
        /**
         * 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
         */
        void setProperties(Properties properties);
    
    }
    

    2.1.2 @Intercepts注解

           Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。@Intercepts注解定义如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
        /**
         * 定义拦截点
         * 只有符合拦截点的条件才会进入到拦截器
         */
        Signature[] value();
    }
    

            Signature来指定咱们需要拦截那个类对象的哪个方法。定义如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
        /**
         * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
         */
        Class<?> type();
    
        /**
         * 在定义拦截类的基础之上,在定义拦截的方法
         */
        String method();
    
        /**
         * 在定义拦截方法的基础之上在定义拦截的方法对应的参数,
         * JAVA里面方法可能重载,不指定参数,不晓得是那个方法
         */
        Class<?>[] args();
    }
    

           我们举一个例子来说明,比如我们自定义一个MybatisInterceptor类,来拦截Executor类里面的两个query。自定义拦截类MybatisInterceptor

    @Intercepts({
            @Signature(
                    type = Executor.class,
                    method = "query",
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
            ),
            @Signature(
                    type = Executor.class,
                    method = "update",
                    args = {MappedStatement.class, Object.class}
            )
    })
    public class MybatisInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
    
            // TODO: 自定义拦截逻辑
    
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this); // 返回代理类
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    
    

    2.2 注册拦截器

           注册拦截器就是去告诉Mybatis去使用我们的拦截器。注册拦截器类非常的简单,在@Configuration注解的类里面,@Bean我们自定义的拦截器类。比如我们需要注册自定义的MybatisInterceptor拦截器。

    /**
     * mybatis配置
     */
    @Configuration
    public class MybatisConfiguration {
    
        /**
         * 注册拦截器
         */
        @Bean
        public MybatisInterceptor mybatisInterceptor() {
            MybatisInterceptor interceptor = new MybatisInterceptor();
            Properties properties = new Properties();
            // 可以调用properties.setProperty方法来给拦截器设置一些自定义参数
            interceptor.setProperties(properties);
            return interceptor;
        }
        
    
    }
    

    三 Mybatis拦截器实例-自定义拦截器

           上面讲了一大堆,最终的目的都是要使用上拦截器,接下来。我们通过几个简单的自定义拦截器来加深对Mybatis拦截器的理解。实例代码在链接地址:https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面。

    3.1 日志打印

           自定义LogInterceptor拦截器,打印出我们每次sq执行对应sql语句。

    3.2 分页

           模仿pagehelper,咱们也来实现一个分页的拦截器PageInterceptor,该拦截器也支持自定义count查询。

    3.3 分表

           自定义拦截器TableShardInterceptor实现水平分表的功能。

    3.4 对查询结果的某个字段加密

           自定义拦截器EncryptResultFieldInterceptor对查询回来的结果中的某个字段进行加密处理。

    上面拦截器的实现,在github https://github.com/tuacy/microservice-framework 的 mybatis-interceptor module里面都能找到具体的实现。


           发现想把Mybatis拦截器的使用讲清楚还是比较难的,因为里面设计的到的东西太多了,用代码才是最好说话的,所以我在实例里面都尽可能的把注解写的很详细。希望能对大家有点帮助。

    展开全文
  • mybatis 拦截器

    2018-06-26 15:42:38
    mybatis拦截器以及JSqlParser解析sql和生成一个新的Sqlsource
  • mybatis拦截器

    千次阅读 2020-09-01 22:36:26
    mybatis拦截器 一、拦截器介绍 Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法...

    mybatis拦截器

    一、拦截器介绍

    Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。所以Mybatis拦截器的使用范围是非常广泛的。

    使用介绍

    @Intercepts({
            @Signature(type = Executor.class, method = "query",
                    args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
    public class DbFlowCtrlInterceptor implements Interceptor {
    
        private static volatile int count = 0;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            count++;
            System.out.println("当前查询数量=" + count);
            if (1000 < count) {
                System.out.println("超过阈值");
            }
            Object proceed = invocation.proceed();
            count--;
            return proceed;
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
        }
    }
    

    meybaits配置文件修改,plugin位置必须遵循下面顺序,在environments之前 configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?

    <plugins>
            <plugin interceptor="maintest.DbFlowCtrlInterceptor"/>
    </plugins>
    

    二、实现原理

    1、读取mybatis-config配置文件,解析plugins节点,将plugin中的拦截器添加到Configuration的InterceptorChain拦截器列表中。

    	/**
         *
         * 功能描述: 解析配置文件拦截器配置
         *
         * @param parent
         * @return
         * @see [相关类/方法](可选)
         * @since [产品/模块版本](可选)
         */
        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);
                }
            }
        }
    

    2、Configuration创建Executor、StatementHandler、ResultSetHandler、ParameterHandler的实例时候如果存在拦截器,会通过动态代理的方式生成这些实例的代理对象

    // 生成执行器代理对象
    executor = (Executor) interceptorChain.pluginAll(executor);
    // 生成statement处理代理对象
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    // 生成resultHandler代理对象
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    // 生成parameterHandler代理对象
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    

    3、InterceptorChain 责任链的方式层层包装代理对象

    	/**
         *
         * 功能描述: 生成代理对象,如果存在多个拦截器的话会生成代理对象的代理对象
         *
         * @param target
         * @return
         * @see [相关类/方法](可选)
         * @since [产品/模块版本](可选)
         */
        public Object pluginAll(Object target) {
            for (Interceptor interceptor : interceptors) {
                // 生成代理对象,代理对象中对需要拦截的方法加入了拦截的处理逻辑
                target = interceptor.plugin(target);
            }
            return target;
        }
    

    4、拦截器中的plugin方法

      // 动态代理包装被代理对象
      Plugin.wrap(target, this);
    
      /**
       *
       * 功能描述: 包装需要被代理的对象
       *
       * @param target
       * @param interceptor
       * @return
       * @see [相关类/方法](可选)
       * @since [产品/模块版本](可选)
       */
      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 > 0) {
          // 返回一个代理对象
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    

    5、Executor、StatementHandler、ResultSetHandler、ParameterHandler执行的时候如果存在拦截器,实际调用的是Plugin的invoke方法

      /**
       *
       * 功能描述: 代理对象执行的时候实际调用的方法
       *
       * @param proxy
       * @param method
       * @param args
       * @return
       * @see [相关类/方法](可选)
       * @since [产品/模块版本](可选)
       */
      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);
        }
      }
    
    展开全文
  • MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截器介绍 MyBatis提供了一种插件(plugin...

    MyBatis拦截器介绍

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

    我们进入官网看一看:

    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语法构建的处理

    拦截器的使用

    拦截器介绍及配置

    首先我们看下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配置:

    这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。

    源码分析

    下面我们分析一下这段代码背后的源码。

    首先从源头->配置文件开始分析:

    XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法:

    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);

    }

    }

    }

    具体的解析代码其实比较简单,就不贴了,主要就是通过反射实例化plugin节点中的interceptor属性表示的类。然后调用全局配置类Configuration的addInterceptor方法。

    public void addInterceptor(Interceptor interceptor) {

    interceptorChain.addInterceptor(interceptor);

    }

    这个interceptorChain是Configuration的内部属性,类型为InterceptorChain,也就是一个拦截器链,我们来看下它的定义:

    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);

    }

    }

    现在我们理解了拦截器配置的解析以及拦截器的归属,现在我们回过头看下为何拦截器会拦截这些方法(Executor,ParameterHandler,ResultSetHandler,StatementHandler的部分方法):

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);

    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);

    return parameterHandler;

    }

    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,

    ResultHandler resultHandler, BoundSql boundSql) {

    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);

    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

    return resultSetHandler;

    }

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);

    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

    return statementHandler;

    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {

    executorType = executorType == null ? defaultExecutorType : executorType;

    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

    Executor executor;

    if (ExecutorType.BATCH == executorType) {

    executor = new BatchExecutor(this, transaction);

    } else if (ExecutorType.REUSE == executorType) {

    executor = new ReuseExecutor(this, transaction);

    } else {

    executor = new SimpleExecutor(this, transaction);

    }

    if (cacheEnabled) {

    executor = new CachingExecutor(executor, autoCommit);

    }

    executor = (Executor) interceptorChain.pluginAll(executor);

    return executor;

    }

    以上4个方法都是Configuration的方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor,ParameterHandler,ResultSetHandler,StatementHandler(其中ParameterHandler和ResultSetHandler的创建是在创建StatementHandler[3个可用的实现类CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler]的时候,其构造函数调用的[这3个实现类的构造函数其实都调用了父类BaseStatementHandler的构造函数])。

    这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll刚才已经介绍过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象

    由于可以拦截StatementHandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理StatementHandler接口实现类中的sql即可,可使用反射实现。

    MyBatis还提供了 @Intercepts和 @Signature关于拦截器的注解。官网的例子就是使用了这2个注解,还包括了Plugin类的使用:

    @Override

    public Object plugin(Object target) {

    return Plugin.wrap(target, this);

    }

    下面我们就分析这3个 "新组合" 的源码,首先先看Plugin类的wrap方法:

    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;

    }

    Plugin类实现了InvocationHandler接口,很明显,我们看到这里返回了一个JDK自身提供的动态代理类。我们解剖一下这个方法调用的其他方法:

    getSignatureMap方法:

    private static Map, Set> getSignatureMap(Interceptor interceptor) {

    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;

    }

    getSignatureMap方法解释:首先会拿到拦截器这个类的 @Interceptors注解,然后拿到这个注解的属性 @Signature注解集合,然后遍历这个集合,遍历的时候拿出 @Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于 @Interceptors注解的 @Signature属性是一个属性,所以最终会返回一个以type为key,value为Set的Map。

    @Intercepts({@Signature(

    type= Executor.class,

    method = "update",

    args = {MappedStatement.class,Object.class})})

    比如这个 @Interceptors注解会返回一个key为Executor,value为集合(这个集合只有一个元素,也就是Method实例,这个Method实例就是Executor接口的update方法,且这个方法带有MappedStatement和Object类型的参数)。这个Method实例是根据 @Signature的method和args属性得到的。如果args参数跟type类型的method方法对应不上,那么将会抛出异常。

    getAllInterfaces方法:

    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()]);

    }

    getAllInterfaces方法解释:根据目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们,返回signatureMap中含有target实现的接口数组。

    所以Plugin这个类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。

    比如MyBatis官网的例子,当Configuration调用newExecutor方法的时候,由于Executor接口的update(MappedStatement ms, Object parameter)方法被拦截器被截获。因此最终返回的是一个代理类Plugin,而不是Executor。这样调用方法的时候,如果是个代理类,那么会执行:

    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);

    }

    }

    没错,如果找到对应的方法被代理之后,那么会执行Interceptor接口的interceptor方法。

    这个Invocation类如下:

    public class Invocation {

    private Object target;

    private Method method;

    private Object[] args;

    public Invocation(Object target, Method method, Object[] args) {

    this.target = target;

    this.method = method;

    this.args = args;

    }

    public Object getTarget() {

    return target;

    }

    public Method getMethod() {

    return method;

    }

    public Object[] getArgs() {

    return args;

    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {

    return method.invoke(target, args);

    }

    }

    它的proceed方法也就是调用原先方法(不走代理)。

    总结

    MyBatis拦截器接口提供的3个方法中,plugin方法用于某些处理器(Handler)的构建过程。interceptor方法用于处理代理类的执行。setProperties方法用于拦截器属性的设置。

    其实MyBatis官网提供的使用 @Interceptors和 @Signature注解以及Plugin类这样处理拦截器的方法,我们不一定要直接这样使用。我们也可以抛弃这3个类,直接在plugin方法内部根据target实例的类型做相应的操作。

    总体来说MyBatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对MyBatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点。

    欢迎工作一到五年想成为Java工程师的朋友们加入Java架构开发:744677563

    群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

    展开全文
  • MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截器介绍 MyBatis提供了一种插件(plugin...

    MyBatis拦截器介绍

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

    我们进入官网看一看:

    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语法构建的处理

    拦截器的使用

    拦截器介绍及配置

    首先我们看下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配置:

    这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。

    源码分析

    下面我们分析一下这段代码背后的源码。

    首先从源头->配置文件开始分析:

    XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法:

    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);

    }

    }

    }

    具体的解析代码其实比较简单,就不贴了,主要就是通过反射实例化plugin节点中的interceptor属性表示的类。然后调用全局配置类Configuration的addInterceptor方法。

    public void addInterceptor(Interceptor interceptor) {

    interceptorChain.addInterceptor(interceptor);

    }

    这个interceptorChain是Configuration的内部属性,类型为InterceptorChain,也就是一个拦截器链,我们来看下它的定义:

    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);

    }

    }

    现在我们理解了拦截器配置的解析以及拦截器的归属,现在我们回过头看下为何拦截器会拦截这些方法(Executor,ParameterHandler,ResultSetHandler,StatementHandler的部分方法):

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);

    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);

    return parameterHandler;

    }

    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,

    ResultHandler resultHandler, BoundSql boundSql) {

    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);

    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

    return resultSetHandler;

    }

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);

    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

    return statementHandler;

    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {

    executorType = executorType == null ? defaultExecutorType : executorType;

    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

    Executor executor;

    if (ExecutorType.BATCH == executorType) {

    executor = new BatchExecutor(this, transaction);

    } else if (ExecutorType.REUSE == executorType) {

    executor = new ReuseExecutor(this, transaction);

    } else {

    executor = new SimpleExecutor(this, transaction);

    }

    if (cacheEnabled) {

    executor = new CachingExecutor(executor, autoCommit);

    }

    executor = (Executor) interceptorChain.pluginAll(executor);

    return executor;

    }

    以上4个方法都是Configuration的方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor,ParameterHandler,ResultSetHandler,StatementHandler(其中ParameterHandler和ResultSetHandler的创建是在创建StatementHandler[3个可用的实现类CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler]的时候,其构造函数调用的[这3个实现类的构造函数其实都调用了父类BaseStatementHandler的构造函数])。

    这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll刚才已经介绍过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象

    由于可以拦截StatementHandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理StatementHandler接口实现类中的sql即可,可使用反射实现。

    MyBatis还提供了 @Intercepts和 @Signature关于拦截器的注解。官网的例子就是使用了这2个注解,还包括了Plugin类的使用:

    @Override

    public Object plugin(Object target) {

    return Plugin.wrap(target, this);

    }

    下面我们就分析这3个 "新组合" 的源码,首先先看Plugin类的wrap方法:

    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;

    }

    Plugin类实现了InvocationHandler接口,很明显,我们看到这里返回了一个JDK自身提供的动态代理类。我们解剖一下这个方法调用的其他方法:

    getSignatureMap方法:

    private static Map, Set> getSignatureMap(Interceptor interceptor) {

    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;

    }

    getSignatureMap方法解释:首先会拿到拦截器这个类的 @Interceptors注解,然后拿到这个注解的属性 @Signature注解集合,然后遍历这个集合,遍历的时候拿出 @Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于 @Interceptors注解的 @Signature属性是一个属性,所以最终会返回一个以type为key,value为Set的Map。

    @Intercepts({@Signature(

    type= Executor.class,

    method = "update",

    args = {MappedStatement.class,Object.class})})

    比如这个 @Interceptors注解会返回一个key为Executor,value为集合(这个集合只有一个元素,也就是Method实例,这个Method实例就是Executor接口的update方法,且这个方法带有MappedStatement和Object类型的参数)。这个Method实例是根据 @Signature的method和args属性得到的。如果args参数跟type类型的method方法对应不上,那么将会抛出异常。

    getAllInterfaces方法:

    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()]);

    }

    getAllInterfaces方法解释:根据目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们,返回signatureMap中含有target实现的接口数组。

    所以Plugin这个类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。

    比如MyBatis官网的例子,当Configuration调用newExecutor方法的时候,由于Executor接口的update(MappedStatement ms, Object parameter)方法被拦截器被截获。因此最终返回的是一个代理类Plugin,而不是Executor。这样调用方法的时候,如果是个代理类,那么会执行:

    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);

    }

    }

    没错,如果找到对应的方法被代理之后,那么会执行Interceptor接口的interceptor方法。

    这个Invocation类如下:

    public class Invocation {

    private Object target;

    private Method method;

    private Object[] args;

    public Invocation(Object target, Method method, Object[] args) {

    this.target = target;

    this.method = method;

    this.args = args;

    }

    public Object getTarget() {

    return target;

    }

    public Method getMethod() {

    return method;

    }

    public Object[] getArgs() {

    return args;

    }

    public Object proceed() throws InvocationTargetException, IllegalAccessException {

    return method.invoke(target, args);

    }

    }

    它的proceed方法也就是调用原先方法(不走代理)。

    总结

    MyBatis拦截器接口提供的3个方法中,plugin方法用于某些处理器(Handler)的构建过程。interceptor方法用于处理代理类的执行。setProperties方法用于拦截器属性的设置。

    其实MyBatis官网提供的使用 @Interceptors和 @Signature注解以及Plugin类这样处理拦截器的方法,我们不一定要直接这样使用。我们也可以抛弃这3个类,直接在plugin方法内部根据target实例的类型做相应的操作。

    总体来说MyBatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对MyBatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点。

    欢迎工作一到五年想成为Java工程师的朋友们加入Java架构开发:744677563

    群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

    展开全文
  • mybatis 拦截器Mybatis拦截器只能拦截四种类型的接口: Executor. StatementHandler.ParameterHandler和ResultSetHandler.这是在Mybatis的Configuration中写死了的,如果要支持拦截其他接口就需要我们重写Mybatis的...
  • 之前写过一篇:Mybatis拦截器实现Geometry类型数据存储与查询主要是关于Mybatis拦截器的使用。从赞的数量看?,可能知道Geometry数据类型的人不多,这种类型是MySQL中处理地理数据的数据类型,比如经纬度等。今天再说...
  • Mybatis 拦截器

    2017-09-13 15:10:50
    1. mybatis拦截器 2个重要的注解  @Intercepts 和 @Signature  @Signature 支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截 2. 应用场景  在做国际化的时候用到了。将数据库中的...
  • 利用Mybatis拦截器对数据库水平分表需求描述当数据量比较多时,放在一个表中的时候会影响查询效率;或者数据的时效性只是当月有效的时候;这时我们就会涉及到数据库的分表操作了。当然,你也可以使用比较完善的第三...
  • MyBatis拦截器介绍MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?我们进入官网看一看:MyBatis 允许你在已映射语句执行过程中的某一点进行拦截...
  • MyBatis拦截器

    2019-07-23 17:30:25
    拦截器的作用 MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,068
精华内容 1,627
关键字:

mybatis拦截器