Mybatis插件原理

本文详细介绍了如何在MyBatis中实现分页插件,包括Page类的定义、分页拦截器的编写以及插件在配置文件中的配置。源码部分解析了InterceptorChain、Interceptor接口、Invocation类和Plugin类的工作原理。
摘要由CSDN通过智能技术生成

一、实现分页插件

(1)编写分页类

public class Page {

  public Page(int pageNo, int pageSize) {
    this.pageNo = pageNo;
    this.pageSize = pageSize;
  }

  public static final int DEFAULT_PAGE_SIZE = 10; // 默认每页记录数

  public static final int PAGE_COUNT = 10;

  private int pageNo = 1; // 页码

  private int pageSize = DEFAULT_PAGE_SIZE; // 每页记录数

  private int totalCount = 0; // 总记录数

  private int totalPage = 0; // 总页数

  private long timestamp = 0; // 查询时间戳

  private boolean full = true; // 是否全量更新 //false 不更新totalcount

  public int getPageNo() {
    return pageNo;
  }

  public void setPageNo(int pageNo) {
    this.pageNo = pageNo;
  }

  public int getPageSize() {
    return pageSize;
  }

  public void setPageSize(int pageSize) {
    this.pageSize = pageSize;
  }

  public int getTotalCount() {
    return totalCount;
  }

  public void setTotalCount(int totalCount) {
    this.totalCount = totalCount;
    int totalPage = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1;
    this.setTotalPage(totalPage);
  }

  public int getTotalPage() {
    return totalPage;
  }

  public void setTotalPage(int totalPage) {
    this.totalPage = totalPage;
  }

  public boolean isFull() {
    return full;
  }

  public void setFull(boolean full) {
    this.full = full;
  }

  public long getTimestamp() {
    return timestamp;
  }

  public void setTimestamp(long timestamp) {
    this.timestamp = timestamp;
  }
}

(2)编写分页拦截器

@Intercepts({
  @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class,
    Integer.class})
})
public class PageInterceptor implements Interceptor {

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    MetaObject meta = SystemMetaObject.forObject(invocation.getTarget());
    StatementHandler delegate = (StatementHandler) meta.getValue("delegate");
    BoundSql boundSql = delegate.getBoundSql();
    Object parameterObject = boundSql.getParameterObject();
    Page page = null;
    if (Page.class.isAssignableFrom(parameterObject.getClass())) {
      page = (Page) parameterObject;
    } else if (Map.class.isAssignableFrom(parameterObject.getClass())) {
      //处理@Param
      Map paramMap = (Map) parameterObject;
      for (Object value : paramMap.values()) {
        if (Page.class.isAssignableFrom(value.getClass())) {
          page = (Page) value;
          break;
        }
      }
    }
    Optional.ofNullable(page)
      .ifPresent(p -> beforeProceed(p, SystemMetaObject.forObject(boundSql), invocation));
    return invocation.proceed();
  }

  private void beforeProceed(Page page, MetaObject sqlMeta, Invocation invocation) {
    String sql = (String) sqlMeta.getValue("sql");
    StringBuffer sqlBuffer = new StringBuffer(sql);
    int offset = (page.getPageNo() - 1) * page.getPageSize();
    sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
    sqlMeta.setValue("sql", sqlBuffer.toString());
    page.setTimestamp(System.currentTimeMillis());
    if (page.isFull()) {
      setPageTotal(page, (Connection) invocation.getArgs()[0], sql);
    }
  }

  private void setPageTotal(Page page, Connection connection, String sql) {
    String countSql = "select count(*) " + sql.substring(sql.toLowerCase().indexOf("from"));
    try
      (Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(countSql)) {
      if (resultSet.next()) {
        page.setTotalCount(resultSet.getInt(1));
      }
    } catch (Exception e) {
    }
  }
}

(3)配置文件配置插件

 <plugins>
    <plugin interceptor="org.apache.ibatis.hzq.plugin.pager.PageInterceptor"/>
  </plugins>

用户自定义的插件只能对MyBatis中的4种组件的方法进行拦截,这4种组件及方法如下:
● Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
● ParameterHandler(getParameterObject, setParameters)
● ResultSetHandler(handleResultSets, handleOutputParameters)
● StatementHandler(prepare, parameterize, batch, update, query)

二、源码解析

(1)拦截器链,在InterceptorChain类中通过一个List对象维护所有的拦截器实例,在InterceptorChain的pluginAll()方法中,会调用所有拦截器实例的plugin()方法,该方法返回一个目标对象的代理对象。

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

(2)拦截器接口

public interface Interceptor {

  //执行拦截逻辑的方法
  Object intercept(Invocation invocation) throws Throwable;

  //决定是否触发intercept()方法
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  //根据配置初始化Interceptor对象
  default void setProperties(Properties properties) {
    // NOP
  }

}

        定义一个拦截器,需要实现Interceptor接口,重写intercept方法,执行拦截逻辑,该方法需要一个Invocation类的对象,Invocation封装了目标类、目标方法和方法参数,可以调用目标方法。

(3)Invocation类

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  ...

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

(4)Plugin.wrap生成目标对象的代理对象,重写invoke方法,在执行目标方法之前执行拦截逻辑

public class Plugin implements InvocationHandler {

  // 目标对象
  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    //key是要拦截的接口,value是拦截的方法
    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;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        //获取当前方法所在类或接口中,可被当前Interceptor拦截的方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

...

}

(5)在Configuration类中的工厂方法,创建Executor、ParameterHandler、ResultSetHandler、StatementHandler的拦截代理对象

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
      BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
        parameterObject, boundSql);
    return (ParameterHandler) interceptorChain.pluginAll(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);
    return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  }

  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);
    return (StatementHandler) interceptorChain.pluginAll(statementHandler);
  }

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : 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);
    }
    return (Executor) interceptorChain.pluginAll(executor);
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值