Mybatis 插件开发与责任链模式

概述

插件是用来改变或者扩展 Mybatis 的原有的功能,Mybatis 的插件就是通过继承 Interceptor 拦截器实现的。在没有完全理解插件之前禁止使用插件对 Mybatis 进行扩展,又可能会导致严重的问题。

Mybatis 中能使用插件进行拦截的接口和方法如下:

  • Executor(update、query 、 flushStatment 、 commit 、 rollback 、 getTransaction 、 close 、 isClose)
  • StatementHandler(prepare 、 paramterize 、 batch 、 update 、 query)
  • ParameterHandler(getParameterObject 、 setParameters )
  • ResultSetHandler(handleResultSets 、 handleCursorResultSets 、 handleOutputParameters)

关键接口

Interceptor

package org.apache.ibatis.plugin;

import java.util.Properties;

public interface Interceptor {

  //执行拦截逻辑的方法,调用代理对象的invoke方法
  Object intercept(Invocation invocation) throws Throwable;

  //target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
  Object plugin(Object target);

  //读取在plugin中设置的参数
  void setProperties(Properties properties);

}

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 getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

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

}

示例:慢查询检测插件

配置类

@Intercepts({
	@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
//	@Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})

public class ThresholdInterceptor implements Interceptor {
	
	private long threshold;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		long begin = System.nanoTime();
		//执行query方法
		Object ret = invocation.proceed();
		long runTime = (System.nanoTime()- begin)/1000;
		if(runTime >= threshold){
			Object[] args = invocation.getArgs();
			//拿到的其实是PreparedStatement的一个动态代理
			//InvocationHandler是PreparedStatementLogger
			Statement stat = (Statement) args[0];
			//反射工具类
			MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
			//还记得h.invoke()吗
			PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
			Statement statement = statementLogger.getPreparedStatement();
			//TODO 日志相关操作
			System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"+runTime+"毫秒,已经超过阈值!");
		}
		return ret;
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		this.threshold = Long.valueOf(properties.getProperty("threshold"));
	}
	
}

关于

@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})

type:拦截的类型
method:方法
args:参数的类型
在这里插入图片描述

配置文件

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>	
 	<plugins>

		<plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor"> 
			<property name="threshold" value="10"/>
		</plugin>
			
<!--  		 <plugin interceptor="com.github.pagehelper.PageInterceptor">
			<property name="pageSizeZero" value="true" />
		</plugin> -->
		
	</plugins>
	
</configuration>  

运行结果

随便做个 select 测试,结果如下
在这里插入图片描述
如果真正用在项目里,阈值应该设置大一些。

原理

责任链模式

责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作;和装饰器模式不同,每个节点都知道后继者是谁;适合为完成同一个请求需要多个处理类的场景。

在这里插入图片描述
角色

  • Handler:定义了一个处理请求的标准接口
  • ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可以结束处理流程,也可以将请求转发给它的后继者

优点

  • 降低耦合度。它将请求的发送者和接收者解耦。
  • 简化了对象。使得对象不需要知道链的结构。
  • 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  • 增加新的请求处理类很方便。

源码

XMLConfigBuilder

类 XMLConfigBuilder 里的 parseConfiguration 方法里,有一步是

//解析<plugins>节点
pluginElement(root.evalNode("plugins"));

跟 pluginElement:

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍历所有的插件配置
      for (XNode child : parent.getChildren()) {
    	//获取插件的类名
        String interceptor = child.getStringAttribute("interceptor");
        //获取插件的配置
        Properties properties = child.getChildrenAsProperties();
        //实例化插件对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置插件属性
        interceptorInstance.setProperties(properties);
        //将插件添加到configuration对象,底层使用ArrayList保存所有的插件
        //这里体现了责任链模式的顺序
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

Configuration

在这里插入图片描述
可以看到 4 种对象的实例化的方法都在这个类里。
具体实例化的方法里都有 interceptorChain.pluginAll 方法。

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);
    }
    //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

InterceptorChain

public class InterceptorChain {

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

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      //注意这里,调用plugin方法,这是自己重写的方法
      target = interceptor.plugin(target);
    }
    return target;
  }

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

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

}

重写的 plugin 方法,其实是参照 ExamplePlugin 的。
在这里插入图片描述

@Intercepts({})
public class ExamplePlugin implements Interceptor {
  private Properties properties;
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }

  public Properties getProperties() {
    return properties;
  }

}

Plugin

public class Plugin implements InvocationHandler {
  //封装的真正提供服务的对象
  private final Object target;
  //自定义的拦截器
  private final Interceptor interceptor;
  //解析@Intercepts注解得到的signature信息
  private final Map<Class<?>, Set<Method>> signatureMap;
 
  //静态方法,用于帮助Interceptor生成动态代理
  public static Object wrap(Object target, Interceptor interceptor) {
	//解析Interceptor上@Intercepts注解得到的signature信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();//获取目标对象的类型
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口(拦截器可以拦截4大对象实现的接口)
    if (interfaces.length > 0) {
      //使用jdk的方式创建动态代理
      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.intercept方法进行拦截处理
        //自己重写的intercept方法,就是在这里被调用
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //如果当前方法不需要被拦截,则调用对象自身的方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

//省略了一些方法
}

相关资料

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md

©️2020 CSDN 皮肤主题: 岁月 设计师: pinMode 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值