Mybatis拦截器机制深入

在进行软件开发过程中总会遇到一些公用代码需要被提取出来,这个时候代理是一个好的解决方案,Mybatis也借助JDK代理实现了一套拦截器机制,可以在被拦截的方法前后加入通用逻辑,并通过@Intercepts和@Signature注解指定需要被代理的接口和方法。

一、实例

场景:需要在插入或修改数据库时动态加入修改时间和修改人,并记录下执行数据库操作花费时间。

1. 实现自定义拦截器
@Intercepts({@Signature(type=Executor.class, method="update", args={MappedStatement.class, Object.class})})
public class MyTestInterceptor implements Interceptor {

	public Object intercept(Invocation invocation) throws Throwable {
		Object arg = invocation.getArgs()[1];
		if(arg instanceof BaseBean) {
			BaseBean bean = (BaseBean) arg;
			bean.setUpdatetime(System.currentTimeMillis());
			bean.setUpdator("login user");
		}
		
		long t1 = System.currentTimeMillis();
		//执行后面业务逻辑
		Object obj = invocation.proceed();
		
		long t2 = System.currentTimeMillis();
		System.out.println("cost time:" + (t2-t1) + "ms");
		return obj;
	}

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

	public void setProperties(Properties properties) {
		System.out.println("这是配置文件传过来的参数: " + properties.get("name"));
	}
}
2. 加入配置文件
<plugins>
	<plugin interceptor="org.apache.ibatis.plugin.MyTestInterceptor">
		<property name="name" value="abc" />
	</plugin>
</plugins>
3. 触发插入操作

看日志updatetime和updator参数是跟intercept中传入的一样

这是配置文件传过来的参数: abc
DEBUG [main] - ==>  Preparing: insert into Author (id,username,password,email,bio,updatetime,updator) values (?,?,?,?,?,?,?) 
DEBUG [main] - ==> Parameters: 500(Integer), 张三(String), ******(String), 张三@222.com(String), Something...(String), 1478778990865(Long), login user(String)
DEBUG [main] - <==    Updates: 1
cost time:773ms

二、源码分析

1. 拦截器接口Interceptor

Interceptor接口是暴露给用户的,通常在自定义Interceptor的plugin方法中实现代理,(其实给Interceptor子类传入被代理对象后理论上可以对该对象做任何事,包括修改数据,替换对象,甚至重新实现一套代理机制等),Mybaits已经实现了代理机制,并且提供了@Intercepts和@Signature使用规则,Mybaits的代理实现封装在Plugin工具类中,只需要调用Plugin的wrap方法即可,看下面测试代码:

public class PluginTest {

	@Test
	public void mapPluginShouldInterceptGet() {
		Map map = new HashMap();
		map = (Map) new AlwaysMapPlugin().plugin(map);
		assertEquals("Always", map.get("Anything"));
	}

	@Intercepts({ @Signature(type = Map.class, method = "get", args = { Object.class }) })
	public static class AlwaysMapPlugin implements Interceptor {
		public Object intercept(Invocation invocation) throws Throwable {
			return "Always";
		}

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

		public void setProperties(Properties properties) {
		}
	}
}

对Map接口的get方法进行了拦截,map.get("Anything")的时候会被拦截进入到AlwaysMapPlugin的intercept方法中,在这里被截断返回Always。

2. 注解规则

@Intercepts和@Signature注解,这两个注解定义了该自定义拦截器拦截哪个接口的哪个方法。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Signature {
	Class<?> type();
	String method();
	Class<?>[] args();
}



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

Signature中有三个属性,分别是目标接口的Class,需要拦截的方法,拦截方法的参数

Intercepts中Signature是个数组,说明可以配置多个拦截规则

3. 代理工具类Plugin

wrap方法中先获取了Interceptor子类上面的@Intercepts和@Signature注解,根据注解可以取到需要被代理的接口,再把这些接口跟代理目标类的接口取交集,并把这些交集接口用JDK实现代理对象并返回,JDK实现代理机制需要一个执行处理类InvocationHandler,Plugin本身也实现了JDK的InvocationHandler类,在构造JDK代理对象的时候传入Plugin对象,

public class Plugin implements InvocationHandler {

	private Object target;
	private Interceptor interceptor;
	private 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) {
		// interceptor上定义的需要被代理的接口及方法
		Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
		// 被代理对象的class
		Class<?> type = target.getClass();
		//上面两者的交集就是需要被代理的接口
		Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
		if (interfaces.length > 0) {
			//使用jdk实现动态代理
			return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
					new Plugin(target, interceptor, signatureMap));
		}
		return target;
	}

	/**
	 * @Intercepts( {  
       @Signature(type = Executor.class, method = "query", args = {  
              				MappedStatement.class, Object.class, RowBounds.class,  
              				ResultHandler.class }),  
       @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) }
       
	 * @param interceptor
	 * @return
	 */
	private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
		Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
		if (interceptsAnnotation == null) { // issue #251
			throw new PluginException(
					"No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
		}
		Signature[] sigs = interceptsAnnotation.value();
		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;
	}

	private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
		Set<Class<?>> interfaces = new HashSet<Class<?>>();
		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()]);
	}
	
	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 interceptor.intercept(new Invocation(target, method, args));
			}
			return method.invoke(target, args);
		} catch (Exception e) {
			throw ExceptionUtil.unwrapThrowable(e);
		}
	}
}

调用代理对象方法是会进入到Plugin的invoke方法,invoke方法为动态代理执行方法,执行invoke方法时会判断是否是拦截的方法,如果是则执行自定义interceptor的intercept方法,并把拦截的目标、方法、参数封装到Invocation中传过去, 这就进入到了自定义拦截器的控制范围,可以在待执行的目标方法前后添加逻辑,如上面例子中的MyTestInterceptor。

4. 代理链InterceptorChain

Mybatis在解析配置文件的时候会把自定义的拦截器加到InterceptorChain中,InterceptorChain中有个interceptors集合, 用InterceptorChain可以对接口进行链接拦截,它的pluginAll实际上就是遍历interceptors集合调用plugin。

5. mybatis使用拦截机制

mybatis内部在创建几个关键对象的时候进行interceptorChain.pluginAll(obj),这些对象的上层接口分别是Executor, ParameterHandler, ResultSetHandler, StatementHandler,这4个接口涵盖了了从获取参数到构造sql,再到执行sql解析结果的过程,所以可以在这个过程的任何一个地方进行拦截,只需要配置好拦截的接口及方法参数即可。

转载于:https://my.oschina.net/chengxiaoyuan/blog/786108

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值