温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

MyBatis拦截器的实现原理是什么及怎么使用

发布时间:2022-08-23 11:23:14 来源:亿速云 阅读:226 作者:iii 栏目:开发技术

本篇内容介绍了“MyBatis拦截器的实现原理是什么及怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    前言

    Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四个类里面的方法,这四个对象在创建的时候才会创建代理。

    用途:实际工作中,可以使用Mybatis拦截器来做一些SQL权限校验、数据过滤、数据加密脱敏、SQL执行时间性能监控和告警等。

     1.使用方法

    以在Spring中创建 StatementHandler.update()方法的拦截器为例:

    @Component
    @Order(1)
    @Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),})
    public class SqlValidateMybatisInterceptor extends PRSMybatisInterceptor {
     
        @Override
        protected Object before(Invocation invocation) throws Throwable {
            String sql="";
            Statement statement=(Statement) invocation.getArgs()[0];
            if(Proxy.isProxyClass(statement.getClass())){
                MetaObject metaObject= SystemMetaObject.forObject(statement);
                Object h=metaObject.getValue("h");
                if(h instanceof StatementLogger){
                    RoutingStatementHandler rsh=(RoutingStatementHandler) invocation.getTarget();
                    sql=rsh.getBoundSql().getSql();
                }else {
                    PreparedStatementLogger psl=(PreparedStatementLogger) h;
                    sql=psl.getPreparedStatement().toString();
                }
            }else{
                sql=statement.toString();
            }
            if(containsDelete(sql)&&!containsWhere(sql)){
                throw new SQLException("不能删除整张表,sql:"+sql);
            }
            return null;
        }
     
        private boolean containsDelete(String sql){
            return sql.contains("delete")||sql.contains("DELETE");
        }
     
        private boolean containsWhere(String sql){
            return sql.contains("where")||sql.contains("WHERE");
        }
    }
    public class PRSMybatisInterceptor implements Interceptor {
     
        Boolean needBreak=false;
     
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object result= before(invocation);
            if(needBreak){
                return result;
            }
            result= invocation.proceed();
            result=after(result,invocation);
            return result;
        }
     
        protected Object before(Invocation invocation) throws Throwable{
            return null;
        }
        protected Object after(Object result,Invocation invocation) throws Throwable{
            return result;
        }
        @Override
        public Object plugin(Object o) {
            return Plugin.wrap(o, this);
        }
        @Override
        public void setProperties(Properties properties) {
        }
    }

    1. 自定义拦截器 实现 org.apache.ibatis.plugin.Interceptor 接口与其中的方法。在plugin方法中需要返回 return Plugin.wrap(o, this)。在intercept方法中可以实现拦截的业务逻辑,改方法的 参数 Invocation中有原始调用的 对象,方法和参数,可以对其任意处理。

    2. 在自定义的拦截器上添加需要拦截的对象和方法,通过注解 org.apache.ibatis.plugin.Intercepts 添加。如示例代码所示:

    Intercepts的值是一个签名数组,签名中包含要拦截的 类,方法和参数。

    2.MyBatis对象的创建

    代理对象指的是:可以被拦截的4个类的实例。

    代理对象创建时需要解析拦截器,从而利用JDK动态代理将拦截器的逻辑织入原始对象。

    DefaultSqlSession中依赖Executor,如果新建的时候会创建executor

    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        ...
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    }
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
      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);
      }
      executor = (Executor) interceptorChain.pluginAll(executor);
      return executor;
    }

    Executor中要用StatementHandler执行sql语句,StatementHandler是调用configuration.newStatementHandler()方法创建的。

    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
     
    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;
    }

    StatementHandler依赖 parameterHandler 和 resultSetHandler,在构造 StatementHandler 时会调用一下方法创建这两个 handler。

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
      ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
      parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
      return parameterHandler;
    }
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    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;
    }

    3.代理对象的创建

    3.1 拦截器的获取

    从对象的创建过程中可以看出 代理 对象的创建时通过 InterceptorChain.pluginAll() 方法创建的。

    MyBatis拦截器的实现原理是什么及怎么使用

    查看 拦截器链 InterceptorChain 发现,其中的拦截器的添加是在 Configuration 中。因为拦截器被声明为Bean了,所以在MyBatis初始化的时候,会扫描所有拦截器,添加到 InterceptorChain 中。

    3.2 代理对象的创建

    从上一步得知代理对象的创建是调用 Interceptor.pugin() 方法,然后调用 Plugin.wrap() 方法

    Interceptor
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    Plugin实现了 InvocationHandler 接口

    MyBatis拦截器的实现原理是什么及怎么使用

     在 Plugin.wrap() 方法中会获取当前拦截器的接口,生成动态代理。

    4. 拦截器的执行过程

    MyBatis拦截器的实现原理是什么及怎么使用

    在动态代理中当代理对象调用方法时,会将方法的调用委托给 InvocationHandler,也就是 Plugin,

    如下图所示;

    MyBatis拦截器的实现原理是什么及怎么使用

     在该方法中 获取拦截器签名中的方法,如果包含当前方法,则调用拦截方法,否则执行原方法的调用。

    5. 拦截器的执行顺序

    拦截器的顺序配置使用 Spring 中的 org.springframework.core.annotation.Order 注解配置。

    order值大的拦截器先执行,order值大的在interceptors中越靠后,最后生成代理,所以先执行。

    MyBatis拦截器的实现原理是什么及怎么使用

    MyBatis拦截器的实现原理是什么及怎么使用

    “MyBatis拦截器的实现原理是什么及怎么使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI