• Mybatis之plugin插件设计原理


      大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。

      我们从插件配置、插件编写、插件运行原理、插件注册与执行拦截的时机、初始化插件、分页插件的原理等六个方面展开阐述。

     一、插件配置

      Mybatis的插件配置在configuration内部,初始化时,会读取这些插件,保存于Configuration对象的InterceptorChain中。

    1 <?xml version="1.0" encoding="UTF-8"?>
    2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    3 <configuration>
    4     <plugins>
    5         <plugin interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
    6             <property name="value" value="100" />
    7         </plugin>
    8     </plugins>
    9 </configuration>
    1 public class Configuration 
    2 {
    3     protected final InterceptorChain interceptorChain = new     
    4     InterceptorChain();
    5 }

    org.apache.ibatis.plugin.InterceptorChain.java源码。

     1 public class InterceptorChain {
     2 
     3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
     4 
     5   public Object pluginAll(Object target) {
     6     for (Interceptor interceptor : interceptors) {
     7       target = interceptor.plugin(target);
     8     }
     9     return target;
    10   }
    11 
    12   public void addInterceptor(Interceptor interceptor) {
    13     interceptors.add(interceptor);
    14   }
    15   
    16   public List<Interceptor> getInterceptors() {
    17     return Collections.unmodifiableList(interceptors);
    18   }
    19 
    20 }

      上面的for循环代表了只要是插件,都会以责任链的方式逐一执行,所谓插件,其实就类似于拦截器。

    二、插件的编写

      插件必须实现org.apache.ibatis.plugin.Interceptor接口。

    1 public interface Interceptor {
    2   
    3   Object intercept(Invocation invocation) throws Throwable;
    4 
    5   Object plugin(Object target);
    6 
    7   void setProperties(Properties properties);
    8 
    9 }

    -intercept()方法:执行拦截内容的地方,拦截目标对象的目标方法的执行

    -plugin()方法:决定是否触发intercept()方法。  作用:包装目标对象,包装就是为目标对象创建一个代理对象

    -setProperties()方法:给自定义的拦截器传递xml配置的属性参数。将插件注册时的property属性设置进来

    下面自定义一个拦截器:

     1 package com.atguigu.mybatis.dao;
     2 
     3 import java.util.Properties;
     4 
     5 import org.apache.ibatis.executor.statement.StatementHandler;
     6 import org.apache.ibatis.plugin.Interceptor;
     7 import org.apache.ibatis.plugin.Intercepts;
     8 import org.apache.ibatis.plugin.Invocation;
     9 import org.apache.ibatis.plugin.Plugin;
    10 import org.apache.ibatis.plugin.Signature;
    11 import org.apache.ibatis.reflection.MetaObject;
    12 import org.apache.ibatis.reflection.SystemMetaObject;
    13 
    14 
    15 /*
    16  * 完成插件签名:
    17  *     告诉Mybatis当前插件用来拦截哪个对象的哪个方法
    18  * 
    19  * */
    20 @Intercepts(
    21         {
    22             @Signature(type=StatementHandler.class,method ="parameterize",args = java.sql.Statement.class )
    23         }
    24         )
    25 public class MyFirstPlugin implements Interceptor
    26 {
    27     /*
    28      * 拦截目标对象的目标方法的执行
    29      * */
    30     @Override
    31     public Object intercept(Invocation invocation) throws Throwable 
    32     {
    33         System.out.println("MyFirstPlugin的intercept方法执行,拦截方法:"+invocation.getMethod());
    34         
    35         //动态改变一下sql的参数,查3号员工,这里偷梁换柱一下查询4号员工
    36         Object target = invocation.getTarget();
    37         System.out.println("当前拦截的对象:"+target);
    38         //拿到:StatementHandler==>ParameterHandler==>parameterObject
    39         //拿到target(拦截对象)的元数据
    40         MetaObject metaObject = SystemMetaObject.forObject(target);
    41         Object value = metaObject.getValue("parameterHandler.parameterObject");
    42         System.out.println("sql语句用的参数是:"+value);
    43         metaObject.setValue("parameterHandler.parameterObject", 4);
    44         
    45         //执行目标方法
    46         Object proceed = invocation.proceed();
    47         
    48         
    49         
    50         //返回执行后的返回值
    51         return proceed;
    52     }
    53 
    54     
    55     /*
    56      * 插件:
    57      * 作用:包装目标对象,包装就是为目标对象创建一个代理对象
    58      * */
    59     @Override
    60     public Object plugin(Object target) 
    61     {
    62         System.out.println("MyFirstPlugin的plugin方法执行,包装的对象(目标对象):"+target);
    63         
    64         
    65         //我们可以借助Plugin的wrap方法来使用当前的拦截器包装我们的目标对象
    66         Object wrap = Plugin.wrap(target, this);
    67         
    68         //返回的就是为当前target创建的动态代理
    69         return wrap;
    70     }
    71 
    72     /*
    73      * setProperties:
    74      *     将插件注册时的property属性设置进来
    75      * 
    76      * */
    77     @Override
    78     public void setProperties(Properties properties) 
    79     {
    80         System.out.println("插件配置的信息:"+properties);
    81     }
    82 
    83 }

    2.1、为什么要写Annotation注解?注解都是什么含义?

      Mybatis规定插件必须编写Annotation注解,是必须,而不是可选。@Intercepts注解:装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截多个方法,自然就是一个@Signature列表。

      type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }  

      解释:要拦截Executor接口内的query()方法,参数类型为args列表。

    2.2、Plugin.wrap(target, this)是干什么的?

      使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept()方法。

    2.3、Mybatis可以拦截哪些接口对象?

      Mybatis只能拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4个接口对象内的方法。

      重新审视interceptorChain.pluginAll()方法:该方法在创建上述4个接口对象时调用,其含义为给这些接口对象注册拦截器功能,注意是注册,而不是执行拦截。

      拦截器执行时机:plugin()方法注册拦截器后,那么,在执行上述4个接口对象内的具体方法时,就会自动触发拦截器的执行,也就是插件的执行。

    2.4、Invocation

    1 public class Invocation {
    2   private Object target;
    3   private Method method;
    4   private Object[] args;
    5 }

      可以通过invocation来获取拦截的目标方法,以及执行目标方法。

    2.5、分页插件原理

      由于Mybatis采用的是逻辑分页,而非物理分页,那么,市场上就出现了可以实现物理分页的Mybatis的分页插件。  要实现物理分页,就需要对String sql进行拦截并增强,Mybatis通过BoundSql对象存储String sql,而BoundSql则由StatementHandler对象获取。

    1 public interface StatementHandler {
    2     <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
    3 
    4     BoundSql getBoundSql();
    5 }
    1 public class BoundSql {
    2    public String getSql() {
    3     return sql;
    4   }
    5 }

      因此,就需要编写一个针对StatementHandler的query方法拦截器,然后获取到sql,对sql进行重写增强。

  • 相关阅读:
    springboot 重写 AuthorizationFilter
    Docker compose
    javap c 字节码含义
    漏洞扫描工具nessus、rapid7 insightvm、openvas安装&简单使用
    共享手机中的VXN流量给其他设备使用
    mobaxterm会话同步
    symbol类型
    Error: Cannot find module 'typescript/package.json'
    关于vue的一些config文件
    关于参数加密
  • 原文地址:https://www.cnblogs.com/baichunyu/p/11208305.html
Copyright © 2020-2023  润新知