经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
MyBatis拦截器的实现原理
来源:jb51  时间:2022/8/22 16:17:22  对本文有异议

前言

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

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

 1.使用方法

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

  1. @Component
  2. @Order(1)
  3. @Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),})
  4. public class SqlValidateMybatisInterceptor extends PRSMybatisInterceptor {
  5. @Override
  6. protected Object before(Invocation invocation) throws Throwable {
  7. String sql="";
  8. Statement statement=(Statement) invocation.getArgs()[0];
  9. if(Proxy.isProxyClass(statement.getClass())){
  10. MetaObject metaObject= SystemMetaObject.forObject(statement);
  11. Object h=metaObject.getValue("h");
  12. if(h instanceof StatementLogger){
  13. RoutingStatementHandler rsh=(RoutingStatementHandler) invocation.getTarget();
  14. sql=rsh.getBoundSql().getSql();
  15. }else {
  16. PreparedStatementLogger psl=(PreparedStatementLogger) h;
  17. sql=psl.getPreparedStatement().toString();
  18. }
  19. }else{
  20. sql=statement.toString();
  21. }
  22. if(containsDelete(sql)&&!containsWhere(sql)){
  23. throw new SQLException("不能删除整张表,sql:"+sql);
  24. }
  25. return null;
  26. }
  27. private boolean containsDelete(String sql){
  28. return sql.contains("delete")||sql.contains("DELETE");
  29. }
  30. private boolean containsWhere(String sql){
  31. return sql.contains("where")||sql.contains("WHERE");
  32. }
  33. }
  34. public class PRSMybatisInterceptor implements Interceptor {
  35. Boolean needBreak=false;
  36. @Override
  37. public Object intercept(Invocation invocation) throws Throwable {
  38. Object result= before(invocation);
  39. if(needBreak){
  40. return result;
  41. }
  42. result= invocation.proceed();
  43. result=after(result,invocation);
  44. return result;
  45. }
  46. protected Object before(Invocation invocation) throws Throwable{
  47. return null;
  48. }
  49. protected Object after(Object result,Invocation invocation) throws Throwable{
  50. return result;
  51. }
  52. @Override
  53. public Object plugin(Object o) {
  54. return Plugin.wrap(o, this);
  55. }
  56. @Override
  57. public void setProperties(Properties properties) {
  58. }
  59. }

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

  1. private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
  2. ...
  3. final Executor executor = configuration.newExecutor(tx, execType);
  4. return new DefaultSqlSession(configuration, executor, autoCommit);
  5. }
  6. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  7. executorType = executorType == null ? defaultExecutorType : executorType;
  8. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  9. Executor executor;
  10. if (ExecutorType.BATCH == executorType) {
  11. executor = new BatchExecutor(this, transaction);
  12. } else if (ExecutorType.REUSE == executorType) {
  13. executor = new ReuseExecutor(this, transaction);
  14. } else {
  15. executor = new SimpleExecutor(this, transaction);
  16. }
  17. if (cacheEnabled) {
  18. executor = new CachingExecutor(executor);
  19. }
  20. executor = (Executor) interceptorChain.pluginAll(executor);
  21. return executor;
  22. }

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

  1. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
  2. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  3. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  4. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  5. return statementHandler;
  6. }

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

  1. this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  2. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  3. ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  4. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  5. return parameterHandler;
  6. }
  1. this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  2. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
  3. ResultHandler resultHandler, BoundSql boundSql) {
  4. ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  5. resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  6. return resultSetHandler;
  7. }

3.代理对象的创建

3.1 拦截器的获取

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

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

3.2 代理对象的创建

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

  1. Interceptor
  2. @Override
  3. public Object plugin(Object o) {
  4. return Plugin.wrap(o, this);
  5. }

Plugin实现了 InvocationHandler 接口

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

4. 拦截器的执行过程

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

如下图所示;

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

5. 拦截器的执行顺序

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

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号