MyBatis拦截器的实现原理

 更新时间:2022年08月22日 14:57:13   作者:CaptHua  
这篇文章主要介绍了MyBatis拦截器的实现原理,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() 方法创建的。

查看 拦截器链 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 接口

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

4. 拦截器的执行过程

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

如下图所示;

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

5. 拦截器的执行顺序

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

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

到此这篇关于MyBatis拦截器的实现原理的文章就介绍到这了,更多相关MyBatis拦截器 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中String和StringBuffer及StringBuilder 有什么区别

    Java中String和StringBuffer及StringBuilder 有什么区别

    这篇文章主要介绍了Java中String和StringBuffer及StringBuilder 有什么区别,String 是 Java 语言非常基础和重要的类,更多相关内容需要的小伙伴可以参考下面文章内容
    2022-06-06
  • Java中线程死亡的几种情况实例分析

    Java中线程死亡的几种情况实例分析

    线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。下面这篇文章主要给大家介绍了Java线程死亡的几种情况,需要的朋友可以参考下。
    2017-01-01
  • java swing中实现拖拽功能示例

    java swing中实现拖拽功能示例

    这篇文章主要介绍了java swing中实现拖拽功能示例,需要的朋友可以参考下
    2014-04-04
  • java类成员中的访问级别浅析

    java类成员中的访问级别浅析

    在本篇文章里小编给大家整理的是一篇关于java类成员中的访问级别浅析内容,有兴趣的朋友们跟着学习下。
    2021-01-01
  • springmvc+spring+mybatis实现用户登录功能(下)

    springmvc+spring+mybatis实现用户登录功能(下)

    这篇文章主要为大家详细介绍了springmvc+spring+mybatis实现用户登录功能的第二篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 深入分析Android系统中SparseArray的源码

    深入分析Android系统中SparseArray的源码

    这篇文章主要介绍了深入分析Android系统中SparseArray的源码,SparseArray为Java实现,需要的朋友可以参考下
    2015-07-07
  • 通过Session案例分析一次性验证码登录

    通过Session案例分析一次性验证码登录

    这篇文章主要介绍了通过Session案例分析一次性验证码登录,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-03-03
  • 详解Spring Security认证流程

    详解Spring Security认证流程

    这篇文章主要介绍了Spring Security认证流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • Springboot项目使用html5的video标签完成视频播放功能

    Springboot项目使用html5的video标签完成视频播放功能

    这篇文章主要介绍了Springboot项目使用html5的video标签完成视频播放功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 如何使用SpringBoot进行优雅的数据验证

    如何使用SpringBoot进行优雅的数据验证

    这篇文章主要介绍了如何使用SpringBoot进行优雅的数据验证,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11

最新评论