mybatis源码学习:插件定义+执行流程责任链

前端之家收集整理的这篇文章主要介绍了mybatis源码学习:插件定义+执行流程责任链前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。


前文传送门:
mybatis源码学习:从SqlSessionFactory到代理对象的生成
mybatis源码学习:一级缓存和二级缓存分析
mybatis源码学习:基于动态代理实现查询全过程

一、自定义插件流程

  1. /**
  2. * 自定义插件
  3. * Intercepts:完成插件签名,告诉mybatis当前插件拦截哪个对象的哪个方法
  4. *
  5. * @author Summerday
  6. */
  7. @Intercepts({
  8. @Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)
  9. })
  10. public class MyPlugin implements Interceptor {
  11. /**
  12. * 拦截目标方法执行
  13. *
  14. * @param invocation
  15. * @return
  16. * @throws Throwable
  17. */
  18. @Override
  19. public Object intercept(Invocation invocation) throws Throwable {
  20. System.out.println("MyPlugin.intercept getMethod: "+invocation.getMethod());
  21. System.out.println("MyPlugin.intercept getTarget:"+invocation.getTarget());
  22. System.out.println("MyPlugin.intercept getArgs:"+ Arrays.toString(invocation.getArgs()));
  23. System.out.println("MyPlugin.intercept getClass:"+invocation.getClass());
  24. //执行目标方法
  25. Object proceed = invocation.proceed();
  26. //返回执行后的返回值
  27. return proceed;
  28. }
  29. /**
  30. * 包装目标对象,为目标对象创建一个代理对象
  31. *
  32. * @param target
  33. * @return
  34. */
  35. @Override
  36. public Object plugin(Object target) {
  37. System.out.println("MyPlugin.plugin :mybatis将要包装的对象:"+target);
  38. //借助Plugin类的wrap方法使用当前拦截器包装目标对象
  39. Object wrap = Plugin.wrap(target,this);
  40. //返回为当前target创建的动态代理
  41. return wrap;
  42. }
  43. /**
  44. * 将插件注册时的properties属性设置进来
  45. *
  46. * @param properties
  47. */
  48. @Override
  49. public void setProperties(Properties properties) {
  50. System.out.println("插件配置的信息:" + properties);
  51. }
  52. }

xml配置注册插件

  1. <!--注册插件-->
  2. <plugins>
  3. <plugin interceptor="com.smday.interceptor.MyPlugin">
  4. <property name="username" value="root"/>
  5. <property name="password" value="123456"/>
  6. </plugin>
  7. </plugins>

二、测试插件

在这里插入图片描述

三、源码分析

1、Inteceptor在Configuration中的注册

关于xml文件的解析,当然还是需要从XMLConfigBuilder中查找,我们很容易就可以发现关于插件的解析:

  1. private void pluginElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. //获取到全类名
  5. String interceptor = child.getStringAttribute("interceptor");
  6. //获取properties属性
  7. Properties properties = child.getChildrenAsProperties();
  8. //通过反射创建实例
  9. Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
  10. //设置属性
  11. interceptorInstance.setProperties(properties);
  12. //在Configuration中添加插件
  13. configuration.addInterceptor(interceptorInstance);
  14. }
  15. }
  16. }
  17. public void addInterceptor(Interceptor interceptor) {
  18. //interceptorChain是一个存储interceptor的Arraylist
  19. interceptorChain.addInterceptor(interceptor);
  20. }

此时初始化成功,我们在配置文件中定义的插件,已经成功加入interceptorChain。

2、基于责任链的设计模式

我们看到chain这个词应该并不会陌生,我们之前学习过的过滤器也存在类似的玩意,什么意思呢?我们以Executor为例,当创建Executor对象的时候,并不是直接new Executor然后返回:

在这里插入图片描述

在返回之前,他进行了下面的操作:

  1. executor = (Executor) interceptorChain.pluginAll(executor);

我们来看看这个方法具体干了什么:

  1. public Object pluginAll(Object target) {
  2. //遍历所有的拦截
  3. for (Interceptor interceptor : interceptors) {
  4. //调用plugin,返回target包装后的对象
  5. target = interceptor.plugin(target);
  6. }
  7. return target;
  8. }

很明显,现在它要从chain中一一取出interceptor,并依次调用各自的plugin方法,暂且不谈plugin的方法,我们就能感受到责任链的功能让一个对象能够被链上的任何一个角色宠幸,真好。

3、基于动态代理的plugin

那接下来,我们就成功进入我们自定义plugin的plugin方法

在这里插入图片描述

  1. //看看wrap方法干了点啥
  2. public static Object wrap(Object target,Interceptor interceptor) {
  3. //获取获取注解的信息,拦截的对象,拦截方法拦截方法的参数。
  4. Map<Class<?>,Set<Method>> signatureMap = getSignatureMap(interceptor);
  5. //获取当前对象的Class
  6. Class<?> type = target.getClass();
  7. //确认该对象是否为我们需要拦截的对象
  8. Class<?>[] interfaces = getAllInterfaces(type,signatureMap);
  9. //如果是,则创建其代理对象,不是则直接将对象返回
  10. if (interfaces.length > 0) {
  11. return Proxy.newProxyInstance(
  12. type.getClassLoader(),interfaces,new Plugin(target,interceptor,signatureMap));
  13. }
  14. return target;
  15. }

getSignatureMap(interceptor)方法:其实就是获取注解的信息,拦截的对象,拦截方法拦截方法的参数。

  1. private static Map<Class<?>,Set<Method>> getSignatureMap(Interceptor interceptor) {
  2. //定位到interceptor上的@Intercepts注解
  3. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  4. //如果注解不存在,则报错
  5. if (interceptsAnnotation == null) {
  6. throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  7. }
  8. //获取@Signature组成的数组
  9. Signature[] sigs = interceptsAnnotation.value();
  10. Map<Class<?>,Set<Method>> signatureMap = new HashMap<Class<?>,Set<Method>>();
  11. for (Signature sig : sigs) {
  12. //先看map里有没有methods set
  13. Set<Method> methods = signatureMap.get(sig.type());
  14. if (methods == null) {
  15. //没有再创建一个
  16. methods = new HashSet<Method>();
  17. //class:methods设置进去
  18. signatureMap.put(sig.type(),methods);
  19. }
  20. try {
  21. //获取拦截方法
  22. Method method = sig.type().getMethod(sig.method(),sig.args());
  23. //加入到set中
  24. methods.add(method);
  25. } catch (NoSuchMethodException e) {
  26. throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e,e);
  27. }
  28. }
  29. return signatureMap;
  30. }

getAllInterfaces(type,signatureMap)方法:确定是否为拦截对象

  1. private static Class<?>[] getAllInterfaces(Class<?> type,Map<Class<?>,Set<Method>> signatureMap) {
  2. Set<Class<?>> interfaces = new HashSet<Class<?>>();
  3. while (type != null) {
  4. //接口类型
  5. for (Class<?> c : type.getInterfaces()) {
  6. //如果确实是拦截的对象,则加入interfaces set
  7. if (signatureMap.containsKey(c)) {
  8. interfaces.add(c);
  9. }
  10. }
  11. //从父接口中查看
  12. type = type.getSuperclass();
  13. }
  14. //最后set里面存在的元素就是要拦截的对象
  15. return interfaces.toArray(new Class<?>[interfaces.size()]);
  16. }

我们就可以猜测,插件只会对我们要求的对象和方法进行拦截

4、拦截方法的intercept(invocation)

确实,我们一路debug,遇到了Executor、ParameterHandler、ResultHandler都没有进行拦截,然而,当StatementHandler对象出现的时候,就出现了微妙的变化,当我们调用代理的方法必然会执行其invoke方法,不妨来看看:

在这里插入图片描述

ok,此时进入了我们定义的intercept方法,感觉无比亲切。

在这里插入图片描述

  1. //调度被代理对象的真实方法
  2. public Object proceed() throws InvocationTargetException,IllegalAccessException {
  3. return method.invoke(target,args);
  4. }

如果有多个插件,每经过一次wrap都会产生上衣个对象的代理对象,此处反射调用方法也是上衣个代理对象的方法。接着,就还是执行目标的parameterize方法,但是当我们明白这些执行流程的时候,我们就可以知道如何进行一些小操作,来自定义方法的实现了。

四、插件开发插件pageHelper

插件文档地址:https://github.com/pagehelper/Mybatis-PageHelper

这款插件使分页操作变得更加简便,来一个简单的测试如下:

1、引入相关依赖

  1. <dependency>
  2. <groupId>com.github.pageHelper</groupId>
  3. <artifactId>pageHelper</artifactId>
  4. <version>5.1.2</version>
  5. </dependency>

2、全局配置

  1. <!--注册插件-->
  2. <plugins>
  3. <plugin interceptor="com.github.pageHelper.PageInterceptor"></plugin>
  4. </plugins>

3、测试分页

  1. @Test
  2. public void testPlugin(){
  3. //查询第一页,每页3条记录
  4. pageHelper.startPage(1,3);
  5. List<User> all = userDao.findAll();
  6. for (User user : all) {
  7. System.out.println(user);
  8. }
  9. }

在这里插入图片描述

五、插件总结

参考:《深入浅出MyBatis技术原理与实战》

  • 插件生成地是层层代理对象的责任链模式,其中设计反射技术实现动态代理,难免会对性能产生一些影响。
  • 插件的定义需要明确需要拦截的对象、拦截方法拦截方法参数。
  • 插件将会改变MyBatis的底层设计,使用时务必谨慎。

猜你在找的Mybatis相关文章