鲁春利的工作笔记,好记性不如烂笔头
转载自:深入浅出Mybatis-插件原理
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
代理链的生成
Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。
下面以Executor为例。Mybatis在创建Executor对象时:
org.apache.ibatis.session.SqlSessionFactory.openSession() org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession() org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(execType, level, false) { final Executor executor = configuration.newExecutor(tx, execType); }
org.apache.ibatis.session.Configuration
package org.apache.ibatis.session; public class Configuration { protected Environment environment; protected String logPrefix; protected Class <? extends Log> logImpl; protected Class <? extends VFS> vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; // 默认的Executor protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; // InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。 protected final InterceptorChain interceptorChain = new InterceptorChain(); 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); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
org.apache.ibatis.plugin.InterceptorChain
package org.apache.ibatis.plugin; /** * @author Clinton Begin */ public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); /** * 每一个拦截器对目标类都进行一次代理 * @param target * @return 层层代理后的对象 */ 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); } }
下面以一个简单的例子来看看这个plugin方法里到底发生了什么。
package com.invicme.common.persistence.interceptor; import java.sql.Connection; import java.lang.reflect.Method; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author lucl * @version 2017-01-09 * * 示例的分页拦截器: * <pre> * @Intercepts用于表明当前的对象是一个Interceptor * @Signature则表明要拦截的接口(type)、方法(method)以及对应的参数类型(args) * type: Mybatis只支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。 * method: 这几个类的方法 * args: 这几个类的方法的参数 * </pre> * */ @Intercepts(value = { @Signature(type=Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type=StatementHandler.class, method="prepare", args={Connection.class}) }) public class SamplePagingInterceptor implements Interceptor { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 拦截方法 * Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。 */ @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); logger.info("target is {}", target.getClass().getName()); Method method = invocation.getMethod(); logger.info("method is {}", method.getName()); Object[] args = invocation.getArgs(); for (Object arg : args) { logger.info("arg is {}", arg); } Object proceed = invocation.proceed(); logger.info("proceed is {}", proceed.getClass().getName()); return proceed; } /** * 用于封装目标对象,该方法可以返回目标对象本身,也可以返回一个它的代理对象。 */ @Override public Object plugin(Object target) { logger.info("target is {}", target.getClass().getName()); return Plugin.wrap(target, this); } /** * 用于在Mybatis配置文件中指定一些属性 */ @Override public void setProperties(Properties properties) { String value = properties.getProperty("sysuser"); logger.info("value is {}", value); } }
每一个拦截器都必须实现上面的三个方法,其中:
1) Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
2) Object plugin(Object target)就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。
3) setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。
注解里描述的是指定拦截方法的签名 [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。
代理链的生成
package com.invicme.common.aop.proxy; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.PluginException; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.invicme.common.persistence.interceptor.SamplePagingInterceptor; /** * * @author lucl * @version 2017-01-18 * * 测试Class对象 * */ public class MainDriver { private static Logger logger = LoggerFactory.getLogger(MainDriver.class); public static void main(String[] args) { SamplePagingInterceptor interceptor = new SamplePagingInterceptor(); wrap(interceptor); } public static Object wrap (SamplePagingInterceptor interceptor) { // 获取拦截器对应的@Intercepts注解 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); { for (Iterator<Class<?>> it = signatureMap.keySet().iterator(); it.hasNext(); ) { Class<?> clazz = it.next(); Set<Method> set = signatureMap.get(clazz); logger.info("【Signature】clazz name is {}", clazz.getName()); for (Iterator<Method> itt = set.iterator(); itt.hasNext(); ) { Method method = itt.next(); logger.info("\tmethod name is {}", method.getName()); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type type : genericParameterTypes) { logger.info("\t\tparam is {}", type); } } } } logger.info("================================================================"); { Configuration configuration = new Configuration(); Executor executor = configuration.newExecutor(null); Class<?>[] interfaces = executor.getClass().getInterfaces(); for (Class<?> clazz : interfaces) { logger.info("【Interface】clazz name is {}", clazz); } } return null; } /** * 获取拦截器对应的@Intercepts注解 * @param interceptor * @return */ private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { 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()); } 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; } }
从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:
package org.apache.ibatis.plugin; 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; import org.apache.ibatis.reflection.ExceptionUtil; /** * @author Clinton Begin */ 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) { // 从拦截器的注解中获取拦截的类名和方法信息 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // 解析被拦截对象的所有接口(注意是接口) Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // 生成代理对象,Plugin对象为该代理对象的InvocationHandler //(InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念) 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)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
这个Plugin类有三个属性:
private Object target; // 被代理的目标类
private Interceptor interceptor; // 对应的拦截器
private Map<Class<?>, Set<Method>> signatureMap; // 拦截器拦截的方法缓存
关于InvocationHandler请参阅:Java反射机制与动态代理
本文出自 “闷葫芦的世界” 博客,请务必保留此出处http://luchunli.blog.51cto.com/2368057/1892881
原文地址:http://luchunli.blog.51cto.com/2368057/1892881