源码分析Mybatis系列目录:spring
源码分析Mybatis MapperProxy初始化【图文并茂】sql
源码分析Mybatis MappedStatement的建立流程apache
【图文并茂】Mybatis执行SQL的4大基础组件详解数组
有了 Mybatis执行SQL的4大基础组件详解 与 源码解析MyBatis Sharding-Jdbc SQL语句执行流程详解两篇文章的铺垫,本文将直奔主题:Mybatis插件机制。微信
舒适提示:本文也是以提问式阅读与探究源码的技巧展现。mybatis
从前面的文章咱们已经知道,Mybatis在执行SQL语句的扩展点为Executor、StatementHandler、ParameterHandler与ResultSetHandler,咱们本节将以Executor为入口,向你们展现Mybatis插件的扩展机制。app
咱们先来看回顾一下Mybatis Executor的建立入口。运维
1public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 2 executorType = executorType == null ? defaultExecutorType : executorType; 3 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 4 Executor executor; 5 if (ExecutorType.BATCH == executorType) { 6 executor = new BatchExecutor(this, transaction); 7 } else if (ExecutorType.REUSE == executorType) { 8 executor = new ReuseExecutor(this, transaction); 9 } else { 10 executor = new SimpleExecutor(this, transaction); 11 } 12 if (cacheEnabled) { 13 executor = new CachingExecutor(executor); 14 } 15 executor = (Executor) interceptorChain.pluginAll(executor); // @1 16 return executor; 17}
代码@1,:使用InterceptorChain.pluginAll(executor)进行拆件化处理。ide
思考:使用该方法调用后,会返回一个什么对象呢?如何自定义拆件,自定义插件如何执行呢?函数
那接下来咱们带着上述疑问,从InterceptorChain类开始进行深刻学习。
从名字上看其大意为拦截器链。
1public Object proceed() throws InvocationTargetException, IllegalAccessException { 2 return method.invoke(target, args); 3}
该方法的主要目的就是进行处理链的传播,执行完拦截器的方法后,最终须要调用目标方法的invoke方法。
记下来中先重点分析一下InterceptorChain方法的pluginAll方法,由于从开头也知道,Mybatis在建立对象时,是调用该方法,完成目标对象的包装。
1public Object pluginAll(Object target) { // @1 2 for (Interceptor interceptor : interceptors) { // @2 3 target = interceptor.plugin(target); 4 // @3 5 } 6 return target; 7}
代码@1:目标对象,须要被代理的对象。
代码@2:遍历InterceptorChain的拦截器链,分别调用Intercpetor对象的Plugin进行拦截(@3)。
那接下来有三个疑问?
问题1:InterceptorChain中的interceptors是从何时初始化的呢,即拦截链中的拦截器从何而来。
问题2:从前面也得知,不管是建立Executor,仍是建立StatementHandler等,都是调用InterceptorChain#pluginAll方法,那是否是拦截器中的拦截器都会做用与目标对象,这应该是有问题的,该如何处理?
问题3:代理对象是如何建立的。
1public void addInterceptor(Interceptor interceptor) { 2 interceptors.add(interceptor); 3}
要想知道interceptors是如何初始化的,咱们只须要查看该方法的调用链便可。
一路跟踪到源头,咱们会发如今初始化SqlSessionFactory时,会解析一个标签plugin,就能够得知,会在SqlSessionFacotry的一个属性中配置全部的拦截器。
具体配置示例以下:
1<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 2 <property name="dataSource" ref="shardingDataSource"/> 3 <property name="mapperLocations" value="classpath*:META-INF/mybatis/mappers/OrderMapper.xml"/> 4 5 <property name="plugins"> 6 <array> 7 <bean id = "teneantInteceptor" class="com.demo.inteceptor.TenaInteceptor"></bean> 8 </array> 9 </property> 10</bean>
问题1已经解决。但后面两个问题彷佛没有什么突破口。因为目前所涉及的三个类,显然不足以给咱们提供答案,咱们先将目光移到InterceptorChain所在包中的其余类,看看其余类的职责如何。
在org.apache.ibatis.plugin中存在以下两个注解类:Intercepts与Signature,从字面意思就是用来配置拦截的方法信息。
但另一个类型Plugin类确引发了个人注意。接下来咱们将重点分析Plugin方法。
其中InvocationHandler为JDK的动态代理机制中的事件执行器,咱们能够隐约阈值代理对象的生成将基于JDK内置的动态代理机制。
Plugin的核心属性以下:
1private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { 2 this.target = target; 3 this.interceptor = interceptor; 4 this.signatureMap = signatureMap; 5 }
注意:其构造函数为私有的,那如何构建Plugin呢,其构造方法为Plugin的镜头方法wrap中被调用。
1public static Object wrap(Object target, Interceptor interceptor) { 2 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // @1 3 Class<?> type = target.getClass(); 4 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // @2 5 if (interfaces.length > 0) { 6 return Proxy.newProxyInstance( // @3 7 type.getClassLoader(), 8 interfaces, 9 new Plugin(target, interceptor, signatureMap)); 10 } 11 return target; 12}
代码@1:获取待包装的Interceptor的方法签名映射表,稍后详细分析。
代码@2:获取须要代理的对象的Class上声明的全部接口。
代码@3:使用JDK内置的Proxy建立代理对象。Proxy建立代理对象的方法声明以下:
1public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h), 2
注意其事件处理器为Plugin,故在动态运行过程当中会执行Plugin的invoker方法。
在进入Plugin#invoker方法学习以前,咱们先重点查看一下getSignatureMap、getAllInterfaces的实现。
1private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { 2 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // @1 3 if (interceptsAnnotation == null) { // issue #251 // @2 4 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); 5 } 6 Signature[] sigs = interceptsAnnotation.value(); // @3 7 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); 8 for (Signature sig : sigs) { 9 Set<Method> methods = signatureMap.get(sig.type()); 10 if (methods == null) { 11 methods = new HashSet<Method>(); 12 signatureMap.put(sig.type(), methods); 13 } 14 try { 15 Method method = sig.type().getMethod(sig.method(), sig.args()); 16 methods.add(method); 17 } catch (NoSuchMethodException e) { 18 throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); 19 } 20 } 21 return signatureMap; 22}
代码@1:首先从Interceptor的类上获取Intercepts注解。
代码@2:若是Interceptor的类上没有定义Intercepts注解,则抛出异常,说明咱们在自定义插件时,必需要有Intercepts注解。
代码@3:解析Interceptor的values属性(Signature[])数组,而后存入HashMap, Set< Method>>容器内。
舒适提示:从这里能够得知:自定义的插件必须定义Intercepts注解,其注解的value值为Signature。
1private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { 2 Set<Class<?>> interfaces = new HashSet<Class<?>>(); 3 while (type != null) { 4 for (Class<?> c : type.getInterfaces()) { 5 if (signatureMap.containsKey(c)) { 6 interfaces.add(c); 7 } 8 } 9 type = type.getSuperclass(); 10 } 11 return interfaces.toArray(new Class<?>[interfaces.size()]); 12}
该方法的实现比较简单,并非获取目标对象所实现的全部接口,而是返回须要拦截的方法所包括的接口。
1public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // @1 2 try { 3 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 4 if (methods != null && methods.contains(method)) { // @2 5 return interceptor.intercept(new Invocation(target, method, args)); // @3 6 } 7 return method.invoke(target, args); // @4 8 } catch (Exception e) { 9 throw ExceptionUtil.unwrapThrowable(e); 10 } 11}
代码@1:首先对其参数列表作一个简单的说明:
代码@2:获取当前执行方法所属的类,并获取须要被拦截的方法集合。
代码@3:若是需被拦截的方法集合包含当前执行的方法,则执行拦截器的interceptor方法。
代码@4:若是不是,则直接调用目标方法的Invoke方法。
从该方法能够看出Interceptor接口的intercept方法就是拦截器自身须要实现的逻辑,其参数为Invocation,在该方法的结束,须要调用invocation#proceed()方法,进行拦截器链的传播。
从目前的学习中,咱们已经了解了Plugin.wrap方法就是生成带来带来类的惟一入口,那该方法在什么地方调用呢?从代码类库中没有找到该方法的调用链,说明该方法是供用户调用的。
再看InterceptorChain方法的pluginAll方法:
1public Object pluginAll(Object target) { // @1 2 for (Interceptor interceptor : interceptors) { // @2 3 target = interceptor.plugin(target); // @3 4 } 5 return target; 6}
该方法会遍历用户定义的插件实现类(Interceptor),并调用Interceptor的plugin方法,对target进行拆件化处理,即咱们在实现自定义的Interceptor方法时,在plugin中须要根据本身的逻辑,对目标对象进行包装(代理),建立代理对象,那咱们就能够在该方法中使用Plugin#wrap来建立代理类。
接下来咱们再来用序列图来对上述源码分析作一个总结:
看到这里,你们是否对上面提出的3个问题都已经有了本身的答案了。
问题1:InterceptorChain中的interceptors是从何时初始化的呢,即拦截链中的拦截器从何而来。
答:在初始化SqlSesstionFactory的时候,会解析属性plugins属性,会加载全部的拦截器到InterceptorChain中。
问题2:从前面也得知,不管是建立Executor,仍是建立StatementHandler等,都是调用InterceptorChain#pluginAll方法,那是否是拦截器中的拦截器都会做用与目标对象,这应该是有问题的,该如何处理?
答案是在各自订阅的Interceptor#plugin方法中,咱们能够根据传入的目标对象,是不是该拦截器关注的,若是不关注,则直接返回目标对象,若是关注,则使用Plugin#wrap方法建立代理对象。
问题3:代理对象是如何建立的?
代理对象是使用JDK的动态代理机制建立,使用Plugin#wrap方法建立。
实践是检验真理的惟一标准,那到底如何使用Mybatis的插件机制呢?
建立自定义的拦截器Interceptor,实现Interceptor接口。
http://www.javashuo.com/article/p-gdccefpe-mh.html
更多文章请关注微信公众号:
一波广告来袭,做者新书《RocketMQ技术内幕》已出版上市:《RocketMQ技术内幕》已出版上市,目前可在主流购物平台(京东、天猫等)购买,本书从源码角度深度分析了RocketMQ NameServer、消息发送、消息存储、消息消费、消息过滤、主从同步HA、事务消息;在实战篇重点介绍了RocketMQ运维管理界面与当前支持的39个运维命令;并在附录部分罗列了RocketMQ几乎全部的配置参数。本书获得了RocketMQ创始人、阿里巴巴Messaging开源技术负责人、Linux OpenMessaging 主席的高度承认并做序推荐。目前是国内第一本成体系剖析RocketMQ的书籍。