MyBatis插件源码解析

MyBatis插件源码解析

这里用到的设计模式可以参考一下这篇文章:https://www.cnblogs.com/qdhxhz/p/11390778.html,这个也是看到别人的文章理解写的,不过我觉得他借鉴的文章写的挺好的:https://www.jianshu.com/p/b82d0a95b2f3,可以照着敲一遍。
先理解用到的设计模式,就比较容易懂MyBatis插件了。

我们知道Mybatis插件本质上是一个拦截器,拦截的是Mybatis的四大对象
(1)ParameterHandler:处理SQL的参数对象
(2)ResultSetHandler:处理SQL的返回结果集
(3)StatementHandler:数据库的处理对象,用于执行SQL语句
(4)Executor:MyBatis的执行器,用于执行增删改查操作

/**
 * @author Clinton Begin
 */
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;
  }

  //调用了这个方法,将拦截器对象添加到List<Interceptor> interceptors集合中
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

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

}

pluginAll方法中遍历了所有的拦截器,我们点击看看有哪些类使用了这个方法
1.0
可以看到调用pluginAll的类刚好是Mybatis拦截的的四大对象,由此可以判断拦截器生效的方法就是pluginAll方法。
pluginAll方法做了什么操作:
循环了List<Interceptor> interceptors集合对象调用了一个interceptor.plugin(target);方法并返回了一个Object对象,我们看看interceptor.plugin(target)的源码是怎样的。

/**
 * @author Clinton Begin
 */
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

点击 Object plugin(Object target);发现了我们自己写的插件类。
1.0
自己编写的插件类:CustomPlugin

/**
 * 自定义插件
 */
@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})
public class CustomPlugin implements Interceptor {

    /**
     * 插件运行的代码,它将代替原有的方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("开始插入数据");
        Object returnObject = invocation.proceed();
        System.out.println("插入数据完成");
        return returnObject;
    }

    /**
     * 使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept()方法。
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 配置自定义相关属性
     * @param properties
     */
    @Override
    public 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);
  }

}

看看plugin方法中写了什么

 public static Object wrap(Object target, Interceptor interceptor) {
 	//1、获得自定义拦截器上的注解信息key为要拦截的接口的Class对象,value为该接口中的Method
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //2、获得目标类中所有接口中被拦截的接口信息
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //3、如果存在接口,则使用JDK动态代理,否则返回被代理对象
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

编写插件注解

@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})

@Intercepts()接口

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}

1.getSignatureMap方法:

public class Plugin implements InvocationHandler {
	//省略其他代码
	private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //获取当前interceptor类的@Intercepts()注解反射数据
    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());
    }
    //获取value得值,也就是@Signature()注解的数据,可以配置多个@Signature()注解
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      //signatureMap中是否有@Signature()中type的值(我写的自定义注解中type是Executor.class)
      //如果没有就添加一个key是type的值,value是HashSet,保证唯一性
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
      	//得到@Signature()中type的值(是一个类)反射的指定方法
        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;
  }
}

signatureMap的数据格式如下:
1.1
key是注解中的type值的引用,value为method的值的方法。

@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})

2.接下来我们来看一下,获得目标类中所有接口中被拦截的接口信息这个方法

    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
public class Plugin implements InvocationHandler {
	//省略其他代码
	private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
	    Set<Class<?>> interfaces = new HashSet<>();
	    //只要type类不为空,就循环
	    while (type != null) {
	      //循环type类的所有接口
	      for (Class<?> c : type.getInterfaces()) {
	      	//判断signatureMap中包不包含这个接口
	        if (signatureMap.containsKey(c)) {
	          //如果包含就添加到set集合中	
	          interfaces.add(c);
	        }
	      }
	      //获得type类的父类
	      type = type.getSuperclass();
	    }
	    //将interfaces 集合转换成数组
	    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

3.就是JDK动态代理了

public class Plugin implements InvocationHandler {
	//省略其他代码
	public static Object wrap(Object target, Interceptor interceptor) {
		//如果存在接口,则使用JDK动态代理,否则返回被代理对象
		if (interfaces.length > 0) {
	      return Proxy.newProxyInstance(
	          type.getClassLoader(),
	          interfaces,
	          /*这里实例化了Plugin类并且存放了代理类,
	          interceptor:责任链中的某一个具体实现类(我这里代表的是CustomPlugin类),
	          signatureMap:CustomPlugin类的@Intercepts注解解析的数据    
	          */
	          new Plugin(target, interceptor, signatureMap));
	    }
	    return target;
	}
} 

这里我们需要注意:

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;
  }
  //省略其他代码
}

是循环插件,每次都将target(被代理对象)传入到 interceptor.plugin 中,第一次生成一个代理对象,第二次循环的时候将第一次代理生成的对象作为参数,生成第二次代理对象,以次循环添加代理对象。动态代理就会生成如下图这样的结构:
1.1
返回的代理对象是$Proxy56@4947(可以理解成pagehelper的代理类),h(Plugin @4943)在生成的代理类是指向Plugin类,Plugin类的参数类型有:

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;
  }
  //省略其他代码
}

target:target是$Proxy56@4937
interceptor:interceptor指向的是PageInterceptor插件。
signatureMap:signatureMap指向的是PageInterceptor插件需要拦截的信息。
注意这个target会指向$Proxy56@4937指向的是上一个代理类(可以理解成CustomPlugin的代理类,我们自己定义的插件)。

我们来看看$Proxy56@4937这个代理类保存了什么数据:

h(Plugin @4930)在生成的代理类是指向Plugin类。
target:target是CachintExecutor@4919执行器。
interceptor:interceptor指向的是CustomPlugin插件。
signatureMap:signatureMap指向的是CustomPlugin插件需要拦截的信息也就是拦截update(修改)的sql。

我的插件在mybatis-config.xml配置文件中的顺序是:

	<plugins>
		<!--自定义的插件-->
        <plugin interceptor="com.example.maybatissource.plugin.CustomPlugin"/>
        <!--分页的插件-->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- config params as the following -->
            <property name="param1" value="value1"/>
        </plugin>
    </plugins>

动态代理类执行顺序简图:
1.2
当有一个sql需要拦截的时候,我自定义的插件类拦截的是update(修改)语句,他会以次执行简图里的插件顺序从外到内:pagehelper->CustomPlugin。

为什么会先是pagehelper插件?
在mybatis-config.xml配置文件中pagehelper插件在最末尾也就是在interceptors集合中是最后一个,所以它先执行

public class InterceptorChain {
	//省略其他代码
	private final List<Interceptor> interceptors = new ArrayList<>();
}

在Plugin 类中的wrap方法中生成动态代理类的时候每次都会new Plugin(target, interceptor, signatureMap),这里的target第一次会是真正的被代理类,后面的几次都会是上一次jdk生成的代理类。接着就会调用有参构造方法为相对应的常量赋值。而pagehelper是最后一个插件,那么Plugin 类中的全局常量interceptor就是分页插件(pagehelper),signatureMap就是分页插件(pagehelper)@Intercepts()注解需要拦截的信息(可以是方法,参数,返回结果,Executor)。

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

我们看到当前分析的这个类Plugin 是实现了InvocationHandler 类的,所以我们找找看实现了invoke的方法

public class Plugin implements InvocationHandler {
	//省略其他代码
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //method.getDeclaringClass()方法返回表示声明由此Method对象表示的方法的类的Class对象。
      //简称当前方法(method)的所在的Class
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //如果有需要拦截的方法
      if (methods != null && methods.contains(method)) {
      	//1、拦截器的interceptor方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //2、直接调用方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
}

if (methods != null && methods.contains(method))
如果有需要拦截,就执行相对应的插件:interceptor.intercept(new Invocation(target, method, args));

如果没有,就执行return method.invoke(target, args);
调用下一个插件也就是我自定义的插件CustomPlugin,以次往下执行所有插件。层层过滤。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值