• MyBatis拦截器实现原理


    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中越靠后,最后生成代理,所以先执行。

     

    6. 拦截器示例

    此拦截器为一个通用的拦截器,封装了具体的拦截器实现,开放出来before和after方法供业务调用。

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

  • 相关阅读:
    Microsoft Releases .NET 7新功能
    C# Thread.Sleep(0)有什么用?
    网工内推 | 实施工程师,有软考证书优先,上市公司,最高14薪
    联邦学习开源框架方案选型
    SQLite 3.4.60 版本发布,带来优化器和函数增强!
    linux-进程管理
    海缆探测仪TSS350(三)
    物联网开发的流程是怎么样的
    机器学习笔记 - 创建CNN + RNN + CTC损失的模型来识别图像中的文本
    【前沿技术RPA】 一文了解UiPath的项目活动设置
  • 原文地址:https://blog.csdn.net/CaptHua/article/details/126417608