mybatis插件机制原理剖析一文章中,我们简单的剖析了mybatis插件的实现的基本原理,但是还是不够完善,比如:如果有多个拦截器要怎么处理,能不能只要实现了MyInterceptor接口,就自动给包装成代理对象,一个拦截器能不能多个方法进行拦截等等....

现在我们就基于上篇文章继续优化改进:

  • 首先,一个拦截器能多个方法进行拦截,将MySignature注解进行改造, 让它可以支持多个方法如下:

    /**
     * 支持多个方法的注解,注意 @Target({})
    */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface MySignature2 {
       // class
       Class<?> type();
    
       String method();
    
       Class<?>[] args();
    }
    
  • 第二步:若想在拦截器使用,在定义一个包装MySignature2的注解,我们定义成如下:

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
    * @ClassName MyIntercepts
    * @Description:
    * @author: 轩逸
    * @date: 2020/9/22 19:54
    */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MyIntercepts2 {
     /**
     * @Title: 支持多个方法签名
     * @Description:
     * @return 
     * @version V1.0
     * @author 轩逸
     * @Date 2020-09-22 19:55
     */
     MySignature2[] value();
    }
    
  • 第三步,改造我们的拦截器接口,使得自动实现该接口的包装成一个代理对象,如下

    import com.xfyang.plugin.MyInvcation;
    
    /**
    * @ClassName MyInterceptor2
    * @Description:
    * @author: 轩逸
    * @date: 2020/9/22 20:05
    */
    public interface MyInterceptor2 {
      /**
       * @return
       * @Title:
       * @Description:拦截
       * @version V1.0
       * @author 轩逸
       * @Date 2020-09-22 9:53
       */
      Object intercept(MyInvcation myInvcation) throws Exception;
    
      /**
       * @Title:
       * @Description:默认把当前的拦截器包装成给代理对象返回
       * @return 
       * @version V1.0
       * @author 轩逸
       * @Date 2020-09-22 20:09
       */
      default Object plugin(Object target) {
      	return MyPlugin2.wrap(target, this);
      }
    }
    
  • 第四步,改造我们的MyPlugin,使其能够支持多个方法的拦截(也即一个拦截器可以多个方法进行拦截)

      import com.xfyang.plugin.MyInvcation;
      import org.apache.ibatis.reflection.ExceptionUtil;
    
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.util.HashMap;
      import java.util.HashSet;
      import java.util.Map;
      import java.util.Set;
    
      /**
       * @ClassName MyPlugin2
       * @Description:
       * @author: 轩逸
       * @date: 2020/9/22 10:21
       */
      public class MyPlugin2 implements InvocationHandler {
      	private final Object target;
      	private final MyInterceptor2 myInterceptor;
      	private final Map<Class<?>, Set<Method>> signatureMap;
    
      	public MyPlugin2(Object target, MyInterceptor2 myInterceptor, Map<Class<?>, Set<Method>>signatureMap) {
      		this.target = target;
      		this.myInterceptor = myInterceptor;
      		this.signatureMap = signatureMap;
      	}
    
      	/**
      	 * @Title:
      	 * @Description: 包装拦截器符合当前调用方法的代理对象
      	 * @return
      	 * @version V1.0
      	 * @author 轩逸
      	 * @Date 2020-09-22 20:13
      	 */
      	public static Object wrap(Object target, MyInterceptor2 myInterceptor) {
      		Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(myInterceptor);
      		Class<?> type = target.getClass();
      		Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
      		if (interfaces.length > 0) {
      			return Proxy.newProxyInstance(
      					type.getClassLoader(),
      					interfaces,
      					new MyPlugin2(target, myInterceptor, signatureMap));
      		}
      		return target;
      	}
    
      	/**
      	 * @Title:
      	 * @Description:执行代理的拦截器方法,并执行目标的真实方法
      	 * @return 
      	 * @version V1.0
      	 * @author 轩逸
      	 * @Date 2020-09-22 20:14
      	 */
      	@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)) {
      				return myInterceptor.intercept(new MyInvcation(target, method, args));
      			}
      			return method.invoke(target, args);
      		} catch (Exception e) {
      			throw ExceptionUtil.unwrapThrowable(e);
      		}
      	}
    
      	/**
      	 * @Title:
      	 * @Description:获取当前拦截器的方法签名
      	 * @return 
      	 * @version V1.0
      	 * @author 轩逸
      	 * @Date 2020-09-22 20:15
      	 */
      	private static Map<Class<?>, Set<Method>> getSignatureMap(MyInterceptor2 interceptor) {
      		MyIntercepts2 myIntercepts2Annotation = interceptor.getClass().getAnnotation(MyIntercepts2.class);
      		if (myIntercepts2Annotation == null) {
      			throw new RuntimeException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
      		}
      		MySignature2[] sigs = myIntercepts2Annotation.value();
      		Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
      		for (MySignature2 sig : sigs) {
      			Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      			try {
      				Method method = sig.type().getMethod(sig.method(), sig.args());
      				methods.add(method);
      			} catch (NoSuchMethodException e) {
      				throw new RuntimeException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      			}
      		}
      		return signatureMap;
      	}
    
      	/**
      	 * @Title:
      	 * @Description:获取符合当前执行方法的的接口
      	 * @return 
      	 * @version V1.0
      	 * @author 轩逸
      	 * @Date 2020-09-22 20:15
      	 */
      	private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
      		Set<Class<?>> interfaces = new HashSet<>();
      		while (type != null) {
      			for (Class<?> c : type.getInterfaces()) {
      				if (signatureMap.containsKey(c)) {
      					interfaces.add(c);
      				}
      			}
      			type = type.getSuperclass();
      		}
      		return interfaces.toArray(new Class<?>[interfaces.size()]);
      	}
      } 
    
  • 第五步,定义一个拦截器连MyInterceptorChain2,吧对的拦截器依次加入进行

      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.List;
    
      /**
       * @ClassName MyInterceptorChain
       * @Description:
       * @author: 轩逸
       * @date: 2020/9/22 20:30
       */
      public class MyInterceptorChain2 {
      	// 拦截器集合
      	private final List<MyInterceptor2> interceptors = new ArrayList<>();
    
      	 // 依次加入到链条中
      	public Object pluginAll(Object target) {
      	    for (MyInterceptor2 interceptor : interceptors) {
      	     target = interceptor.plugin(target);
                }
      	   return target;
      	}
    
       // 加入拦截器
      public void addInterceptor(MyInterceptor2 interceptor) {
      	interceptors.add(interceptor);
      }
    
       // 提供给外面一个无法修改的拦截器集合
       public List<MyInterceptor2> getInterceptors() {
      	return Collections.unmodifiableList(interceptors);
       }
    }
    
  • 第六步,定义一个拦截器SQLTimeConsumeInterceptor2.

     import com.xfyang.plugin.MyInvcation;
     import com.xfyang.plugin.QueryService;
    
      /**
       * @ClassName SQLTimeConsumeInterceptor2
       * @Description:
       * @author: 轩逸
       * @date: 2020/9/22 20:25
       */
      @MyIntercepts2({
      		@MySignature2(type = QueryService.class, method = "queryByName", args = {String.class}),
      		@MySignature2(type = QueryService.class, method = "updateByName", args = {String.class}),
      })
      public class SQLTimeConsumeInterceptor2 implements MyInterceptor2 {
      	/**
      	 * @param myInvcation
      	 * @return
      	 * @Title:
      	 * @Description:拦截
      	 * @version V1.0
      	 * @author 轩逸
      	 * @Date 2020-09-22 9:53
      	 */
      	@Override
      	public Object intercept(MyInvcation myInvcation) throws Exception {
      		long beginTime = System.currentTimeMillis();
      		Object object = myInvcation.process();
      		System.out.println("SQLTimeConsumeInterceptor2-->当前方法名:" + myInvcation.getMethod().getName() + ",SQL耗时:" + (System.currentTimeMillis() - beginTime));
      		return object;
      	}
      }
    
  • 最后一步,执行测试,首先验证一个拦截器针对一个接口中的2个方法是否OK?

      import com.xfyang.plugin.QueryService;
      import com.xfyang.plugin.QueryServiceImpl;
    
      /**
       * @ClassName MyPlugin2Test
       * @Description:
       * @author: 轩逸
       * @date: 2020/9/22 20:25
       */
      public class MyPlugin2Test {
      	public static void main(String[] args) {
    
      		//通过代理答应执行耗时:
      		QueryService queryService = new QueryServiceImpl();
    
    
      		//包装代理
      		QueryService proxyQueryService = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
      		System.out.println(proxyQueryService.queryByName("12345"));
    
      		//包装代理
      		QueryService proxyQueryService2 = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
      		System.out.println(proxyQueryService2.updateByName("12345"));
      	}
      }
    

控制台打印接口如下,符合预期,一个拦截器针对一个接口类中的2个方法都生效了。 mybatis插件机制原理剖析二_mybatis

其次,验证多个拦截器针对同一个接口中的方法进行层层代理拦截。

package com.xfyang.plugin2;

import com.xfyang.plugin.QueryService;
import com.xfyang.plugin.QueryServiceImpl;

/**
 * @ClassName MyPlugin2Test
 * @Description:
 * @author: 轩逸
 * @date: 2020/9/22 20:25
 */
public class MyPlugin2Test {
	public static void main(String[] args) {
		//testProxyQueryByOneInterceptor()

		//创建目标对象
		QueryService queryService = new QueryServiceImpl();

		//添加到拦截器连中
		MyInterceptorChain2 myInterceptorChain2 = new MyInterceptorChain2();
		myInterceptorChain2.addInterceptor(new SQLTimeConsumeInterceptor2());
		myInterceptorChain2.addInterceptor(new SQLTimeConsumeInterceptor3());

		//依次创建拦截器的代理对象(层层代理)
		QueryService proxyQueryService = (QueryService) myInterceptorChain2.pluginAll(queryService);
		System.out.println(proxyQueryService.queryByName("xyfang"));
	}


	public void testProxyQueryByOneInterceptor() {
		//通过代理答应执行耗时:
		QueryService queryService = new QueryServiceImpl();
		//包装代理
		QueryService proxyQueryService = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
		System.out.println(proxyQueryService.queryByName("12345"));

		//包装代理
		QueryService proxyQueryService2 = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
		System.out.println(proxyQueryService2.updateByName("12345"));
	}
}

验证结果,符合预期:

mybatis插件机制原理剖析二_mybatis_02

在目前实现类中加入:

mybatis插件机制原理剖析二_mybatis_03

mybatis插件机制原理剖析二_mybatis_04

mybatis插件机制原理剖析二_mybatis_05

执行如下:

mybatis插件机制原理剖析二_mybatis_06

可能有人对结果,不是很理解:

    SQLTimeConsumeInterceptor3-->开始执行
    SQLTimeConsumeInterceptor2-->开始执行
    执行方法: queryByName xyfang
    SQLTimeConsumeInterceptor2-->当前方法名:queryByName,SQL耗时:1000
    SQLTimeConsumeInterceptor3-->当前方法名:queryByName,SQL耗时:1000
    查询名称:xyfang

那是因为:SQLTimeConsumeInterceptor3 是对SQLTimeConsumeInterceptor2的代理,SQLTimeConsumeInterceptor2在对QueryService进行代理,从而执行目标类QueryServiceImpl中的queryByName方法, 所以 SQLTimeConsumeInterceptor3-->开始执行是最开始执行的,返回的时候就SQLTimeConsumeInterceptor3-->当前方法名:queryByName,SQL耗时:1000最后返回打印了。

下一篇,我将画个时序图加深下印象,加深了解,就能理解为啥打印是这样的,请看下一篇。

有需要转载的同学,请注明来源、作者等,谢谢!