阅读 132

MyBatis(八):MyBatis插件机制详解

  1. MyBatis插件插件机制简介

    MyBatis插件其实就是为使用者提供的自行拓展拦截器,主要是为了可以更好的满足业务需要。

    在MyBatis中提供了四大核心组件对数据库进行处理,分别是Executor、Statement Handler、ParameterHandler及ResultSetHandler,同时也支持对这四大组件进行自定义扩展拦截,用来增强核心对象的功能。其本质上是使用底层的动态代理来实现的,即程序运行时执行的都是代理后的对象。

    MyBatis允许拦截的方法如下:

    • 执行器Executor (update、query、commit、rollback等方法);

    • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);

    • 参数处理器ParameterHandler (getParameterObject、setParameters方法);

    • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

  2. 拦截器参数简介

    • 拦截器注解

      @Intercepts(//可配置多个@Signature,均使用此类进行拦截增强  {        @Signature(//指定需要拦截的类、方法及方法参数          type = Executor.class,//需要拦截接口          method = "update",//需要拦截方法名称          args = {MappedStatement.class, Object.class}//拦截方法的请求参数        )	})
    • 实现Interceptor接口,并实现方法

      public Object intercept(Invocation invocation)//每次拦截到都会执行此方法,方法内写增强逻辑  invocation//代理对象,可以获取目标方法、请求参数、执行结果等  invocation.proceed() //执行目标方法

      public Object plugin(Object target) 
        Plugin.wrap(target,this)//包装目标对象,为目标对象创建代理对象,将当前生成的代理对象放入拦截器链中

      public void setProperties(Properties properties)//获取配置文件中的插件参数,插件初始化时调用一次
  3. 自定义插件

    • 新建MyExecuter类,实现Interceptor接口

      package com.rangers.plugin;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.*;import java.lang.reflect.Method;import java.util.Properties;/** * @Author Rangers * @Description 自定义Executor update方法 * @Date 2021-03-11 **/@Intercepts({        @Signature(type = Executor.class,                    method = "update",                    args = {MappedStatement.class, Object.class}        )})public class MyExecuter implements Interceptor {    // 接收插件的配置参数    private Properties properties = new Properties();    // 增强逻辑写在此方法中    @Override    public Object intercept(Invocation invocation) throws Throwable {        // 打印插件的配置参数        System.out.println("插件的配置参数:"+properties.toString());        // 获取目标方法Method对象        Method method = invocation.getMethod();        // 获取目标方法的请求参数 与args列表一一对应        String reqParam = invocation.getArgs()[1].toString();        System.out.println("方法名称:"+method.getName()+" 请求参数:"+ reqParam);        // 执行目标方法        Object result = invocation.proceed();        System.out.println("方法名称:"+method.getName()+" 执行结果:"+result);        return result;    }    /**     * @Author Rangers     * @Description      **/    @Override    public Object plugin(Object target) {        //System.out.println("需要包装的目标对象:"+target+" 目标对象类型"+ target.getClass());        // 主要是将当前生成的代理对象放入拦截器链中,包装目标对象,为目标对象创建代理对象        return Plugin.wrap(target,this);    }    /**     * @Author Rangers     * @Description 获取配置文件中的插件属性参数,插件初始化时调用一次     **/    @Override    public void setProperties(Properties properties) {        // 将配置参数进行接收        this.properties = properties;    }}
    • 主配置文件添加标签

      <plugins>  	<!--指定拦截器类-->    <plugin interceptor="com.rangers.plugin.MyExecuter">      	<!--配置拦截器 属性参数-->        <property name="param1" value="value1"/>        <property name="param2" value="value2"/>        <property name="param3" value="value3"/>    </plugin></plugins>
  4. 插件原理

    a、在Executor、StatementHandler、ParameterHandler及ResultSetHandler四大对象创建时,并不是直接返回的,而是中间多了一步interceptorChain.pluginAll()(均在Configuration类中进行创建)。

    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;}

    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;}

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  return parameterHandler;}

    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;}

    b、interceptorChain.pluginAll()调用的就是实现了Interceptor接口的plugin()方法,plugin()方法又通过Plugin.wrap(target,this)为目标对象创建一个Plugin的代理对象,添加到拦截链interceptorChain中。具体代码如下:

      public Object pluginAll(Object target) {    for (Interceptor interceptor : interceptors) {      // 调用实现Interceptor接口的plugin方法      target = interceptor.plugin(target);    }    return target;  }

    @Overridepublic Object plugin(Object target) {  	// 调用Plugin的wrap()方法,创建代理对象    return Plugin.wrap(target,this);}

    public static Object wrap(Object target, Interceptor interceptor) {  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  Class<?> type = target.getClass();  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  // 当前类的接口中如果存在可以被拦截的组件接口,则为其创建代理对象  if (interfaces.length > 0) {    return Proxy.newProxyInstance(        type.getClassLoader(),        interfaces,        new Plugin(target, interceptor, signatureMap));  }  // 否则返回目标对象  return target;}

    c、Plugin实现了 InvocationHandler接口,因此它的invoke方法会拦截所有的方法调用。invoke()方法会 对所拦截的方法进行检测,以决定是否执行插件逻辑。

    @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      // 根据组件对象Class从signatureMap中获取到需要拦截的方法set集合      Set<Method> methods = signatureMap.get(method.getDeclaringClass());      // 若包含当前方法则进行拦截增强      if (methods != null && methods.contains(method)) {        return interceptor.intercept(new Invocation(target, method, args));      }      return method.invoke(target, args);    } catch (Exception e) {      throw ExceptionUtil.unwrapThrowable(e);    }  }

    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {  // 获取到所有拦截器的类  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);  // issue #251  if (interceptsAnnotation == null) {    throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
      }  // 获取到@Signature注解数组  Signature[] sigs = interceptsAnnotation.value();  // signatureMap存放所有拦截到方法,key为四大组件的Class,value为组件对应的方法set集合  Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();  for (Signature sig : sigs) {    Set<Method> methods = signatureMap.get(sig.type());    if (methods == null) {      methods = new HashSet<Method>();      signatureMap.put(sig.type(), methods);    }    try {      Method method = sig.type().getMethod(sig.method(), sig.args());      methods.add(method);    } catch (NoSuchMethodException e) {      throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);    }  }  return signatureMap;}
    • ResultSetHandler—interceptorChain.pluginAll(resultSetHandler)

    • ParameterHandler—interceptorChain.pluginAll(parameterHandler);

    • StatementHandler—interceptorChain.pluginAll(statementHandler);

    • Executor—interceptorChain.pluginAll(executor);

  5. PageHelper插件

    PageHelper是MyBaits框架使用最广泛的第三方物理分页插件,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

    使用步骤:

    • 添加依赖

      <dependency>    <groupId>com.github.pagehelper</groupId>    <artifactId>pagehelper</artifactId>    <version>5.1.8</version></dependency><dependency>    <groupId>com.github.jsqlparser</groupId>    <artifactId>jsqlparser</artifactId>    <version>1.2</version></dependency>
    • 配置插件

      <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    • 使用分页

      @org.junit.Testpublic void testPagehealper() {  PageHelper.startPage(1, 2);  List<User> users = userDao.findAll();  if (users != null && users.size() > 0) {    for (User user : users) {      System.out.println(user.toString());    }    PageInfo<User> pageInfo = new PageInfo<>(users);    System.out.println("总条数:" + pageInfo.getTotal());    System.out.println("总页数:" + pageInfo.getPages());    System.out.println("当前页:" + pageInfo.getPageNum());    System.out.println("每页显万长度:" + pageInfo.getPageSize());    System.out.println("是否第一页:" + pageInfo.isIsFirstPage());    System.out.println("是否最后一页:" + pageInfo.isIsLastPage());  }}


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐