【云+社区年度征文】借鉴了Mybatis源码解决了项目上线时的一个问题

使用了我开发的框架,项目部署时突然出了问题,借鉴了Mybatis源码才解决

一、背景

本篇文章是我对Swagger进行了二次开发,并封装成了一个框架,发布到了maven私服,这样就可以达到拿来即用啦。但是出现了一个问题,导致打包成jar包之后某些功能无法生效,本文特针对这个问题,来阐述如何借鉴了Mybatis源码才解决的。

对swagger扩展的功能,我也对其来龙去脉,做了严谨的分析和相关扩展功能的开发,并记录下来做了梳理,并整理成了一篇文章,在技术创作101训练营第一季的时候,发布到了腾讯云+社区,参加了活动:

这篇名字:《历经14天自定义3个注解解决项目的3个Swagger难题

链接地址:https://cloud.tencent.com/developer/article/1700641

内容大纲:

1608440914350-1fe1ac74-110d-4e39-9029-2a24aafc52fd.png

这篇文章也收获了很多赞和收藏:

1608441319486-24d07b2c-9823-410e-864d-200ba9840d1a.png

同时也被评为技术创作101训练营-第一季的前10名,拿到了“优秀创作者”的荣誉证书。

1608441764160-d737746f-e95f-485e-9fa8-8f913b746dbb.png

在《历经14天自定义3个注解解决项目的3个Swagger难题》中的第三部分:(实战的(二)实战二:减少在Controller中Swagger的代码,使其可以从某些文件中读取信息,自动配置Swagger的功能)。这部分在上线部署的时候突然出现了问题。随后会详细分析一下。

1608441066768-f67a25e0-d6af-49b4-8796-aaee526b6dc9.png

二、分析问题

部署服务之后,发现自己扩展的一个功能尚未生效。

就去docker上扒拉了一下日志。

先看下图,下图是我从docker的启动日志中扒拉出来的。因为我们的服务都部署在了docker中

1608447658780-cd7817ca-4969-45f6-9745-2d2f860af665.png

看一下ReadFromFile.java这个的第298行:

是我读取文件的,初次猜测文件没有读取成功,才会造成的这个问题。

1608448093655-02ae9aa9-90e7-4156-a3db-33cead32fd14.png

为了验证这个问题是不是服务器上某些配置导致的,我从cmd中使用java -jar命令运行了一下jar包:

还是不行

1608448886606-6f26a068-2ab8-4c6b-b9ee-985253a396e9.png

但是在IDEA中启动时是没有问题的。

整个结构是这样的:

1608449649230-17c94c55-7d83-4058-ac4e-06cc0dbdeeaa.png

说的有点多,总结一下:

出现的问题是我所开发的这个框架,以一个依赖的形式被其他项目所使用的时候,在IDEA开发环境下运行是可以的。

其中有一个功能是需要读取项目中的某些文件。但是此功能在项目被打成jar包部署在服务器的时候,却出现了问题,无法正常读取文件。

三、解决方案

遇到问题,肯定先百度一下,谷歌一下。

连续搜了好多天,也连续尝试了好多天;

1608449865266-84cf0339-c82d-41ae-a405-66b55279a3cb.png

别看下面我差出来了很多,但是几乎也就那几种办法。

我几乎都尝试过,但是都不是我想要的。

我想要的还很特殊:

因为我是开发的一个框架,需要从jar包中读取另一个jar包中的某些文件。

1608450190175-e615fce9-cf74-4ccf-9aac-df77da626f59.png

最后不得不放弃百度,就在要快放弃的时候,突然想到Mybatis和我这个是类似的,Mybatis也是一个jar包,Mybatis也是从jar包中读取xxxxMapper.xml文件进行解析的。

Mybatis的这种读取xxxMapper.xml文件的模式和我开发的框架读取md文件的设计居然一模一样。

那只好扒拉一下Mybatis的源代码进行研究一番了;以前想着去一下Mybatis的源码呢,一直没时间。现在正好,趁机学习一下Mybatis源代码。

我从搭建Mybatis的环境开始研究,

也做了比较详细的记录,如果你想学习Mybatis的话,可以持续关注我,我会把学习的过程中记录的一些东西都发布来的。

阅读链接:https://www.yuque.com/docs/share/fa80a044-49be-44ba-aa36-53cca12eee97? 《1、构建源码环境》

1608450637716-74f36f91-9fbc-4c87-bf69-1db231e083e0.png

解决方案:

  • 1、阅读Mybatis源码,找出来Mybatis如何读取的Mapper.xml文件;
  • 2、模仿Mybatis读取Mapper.xml文件的流程去改造自己的程序。

四、分析:Mybatis如何加载xxxMapper.xml文件

为了减少篇幅,本篇文章略过如何搭建Mybatis的源码环境,直接描述分析的过程;如果想看,也可以加我微信:weiyi3700,给你初稿版,或者等我整理出来Mybatis源码系列的文章,再来看也是可以的。

https://www.yuque.com/docs/share/fa80a044-49be-44ba-aa36-53cca12eee97? 《1、构建源码环境》

(一)创建一个可以跟踪的程序

为了好跟踪Mybatis源码,使用IDEA创建了2个Model,一个是Mybatis源码项目,一个是测试程序。

1608451493409-5ae89b54-0821-41fb-9cfc-d51b8df4cc69.png

如果搭建这套环境可以参考,我写的这篇文章,已经放在语雀上了:

https://www.yuque.com/docs/share/fa80a044-49be-44ba-aa36-53cca12eee97? 《1、构建源码环境》

如果能访问数据,则成功:

1608452436960-a5bd1769-44a8-465f-a74a-e1704ff73ae3.png

(二)逐行分析

1、读取mybatis-config.xml文件

代码如下:

String resource = "mybatis-config.xml";
final Reader reader = Resources.getResourceAsReader(resource);

用鼠标点开getResourceAsReader的时候,就会看到如下图所示的:

可以看到,就是去使用流的形式去读取这个配置文件,并返回一个流对象

1608453321997-15d97c66-7805-4d2e-b547-f11f833dca0b.png

在源码中的执行流程如下:

1608455664761-d68535c7-3b74-4a3b-b31b-97089b622571.png

2、创建SqlSessionFactory

SqlSessionFactory的创建的流程太复杂了,我简单总结一下步骤:

(1)从Reader的流中读取Mybatis-config.xml配置文件的数据流;

(2)从流中读取 xml配置文件中的," <configuration>  .... </configuration> " 根节点。

(3)从“<configuration>  .... </configuration>”根节点中解析每个子节点的数据,例如:“mappers”、“environments”节点等;

(4)解析“mappers”节点,拿到xxxMapper.xml的存放方式和存放路径;

(5)按照“mappers”节点中配置的信息,选择性的进行读取Mapper.xml文件;

(5-1)如果是package方式的话:

(5-1-1)先判断是否为jar包,如果是就以流的形式打开;

(5-1-2)然后会把所有的资源扫描一遍,边扫描边检查是否为我们要寻找的路径,例如:com.truedei.mapper;

(5-1-3)把符合要求的url都会添加到resources中返回。

分析具体流程图

整理出了一个流程图,可以看一下:

1608461038875-c9452b26-1b12-48f3-9994-e0b6c2d9a48c.png

到此位置,我们也就知道了,Mybatis是如何扫描到的Mapper.xml文件。

五、总结Mybatis如何扫描到的Mapper.xml文件

我们比较关心的是如何从jar包中扫描到我们想要的资源路径。

由上面的分析可见,是通过ResolverUtil类中的find()方法扫描的。

ResolverUtil.find()又调用VFS类的 VFS.getInstance().list(path)方法进行扫描

在VFS.getInstance().list(path)中比较重要的又两个方法,

一个是:getResources(path)

*(核心)一个是:list(url, path)

在list()接口中进行查找相应的path,并返回一个List<String>集合

而在list()接口中,重要的就是listResources()这个方法了:

在这个方法中,从jar文件流中查找包含path的url;
如果符合要求就添加到List中返回。

 protected List<String> listResources(JarInputStream jar, String path) throws IOException {
    // Include the leading and trailing slash when matching names
    if (!path.startsWith("/")) {
      path = "/" + path;
    }
    if (!path.endsWith("/")) {
      path = path + "/";
    }

    // Iterate over the entries and collect those that begin with the requested path
    List<String> resources = new ArrayList<>();
    for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) {
      if (!entry.isDirectory()) {
        // Add leading slash if it's missing
        StringBuilder name = new StringBuilder(entry.getName());
        if (name.charAt(0) != '/') {
          name.insert(0, '/');
        }

        // Check file name
        if (name.indexOf(path) == 0) {
          if (log.isDebugEnabled()) {
            log.debug("Found resource: " + name);
          }
          // Trim leading slash
          resources.add(name.substring(1));
        }
      }
    }
    return resources;
  }

由此可见,最中间,最核心的就是上面这段代码了。

下面name.indexOf(path)就是用来匹配是否开头包含这个路径的

        // Check file name
        if (name.indexOf(path) == 0) {
          if (log.isDebugEnabled()) {
            log.debug("Found resource: " + name);
          }
          // Trim leading slash
          resources.add(name.substring(1));
        }

那么可以猜测一下,可以修改成我们需要的,例如我想查找包含.md为后缀的文件的路径,那么就可以修改成:

// Check file name
if (name.indexOf(path) > 0) {
    if (log.isDebugEnabled()) {
        log.debug("Found resource: " + name);
    }
    // Trim leading slash
    resources.add(name.substring(1));
}

可以看到,我们最终想要的,可以模仿的有两个核心的java类:

ResolverUtil.java

VFS.java

这两个类,刚好是Mybatis的i/o模块中的:

二话不说,就拷贝到了我开发的框架的项目中:

1608462861538-6d536665-6ecf-4e27-a3d7-0f3ad1ac5ef2.png

六、修改成自己想要的

ResolverUtil中的find方法:

  /**
   * 查找包下的资源
   * @param packageName
   * @return
   */
  public ResolverUtil find(String packageName) {
    //把com.truedei  形式的路径 变成:com/truedei
    String path = getPackagePath(packageName);

    try {

      List<String> children = VFS.getInstance().list(path);

      for (String child : children) {

        if (child.endsWith(".class")) {
          //组装成一个新的文件路径
          child = "/md"+child.substring(child.lastIndexOf('/')).replace("class","md");
          //child就是我想要加载的md文件的路径
          loadJarFileByFile(child);
        }

      }
    } catch (IOException ioe) {
      System.out.println("Could not read package: " + packageName+"-->"+ioe);
    }

    return this;
  }

自己写的:

 /**
   * 加载jar包的资源文件
   * @param file
   * @return
   */
  private  void  loadJarFileByFile(String file) {
    InputStream stream = this.getClass().getResourceAsStream(file);

    if(stream==null){
      return ;
    }

    BufferedReader br = null ;
    try {
      br = new BufferedReader(new InputStreamReader(stream,"UTF-8")) ;
      String s=null ;
      while((s=br.readLine()) !=null){
        //把每一行数据都添加到全局的List中后期对其方便处理
        fileContentList.add(s);
      }
      br.close();
    } catch (FileNotFoundException e) {
      System.out.println("FileNotFoundException:"+e);
    } catch (IOException e) {
      System.out.println("IOException :"+e);
    }finally {
      if(br !=null){
        try {
          br.close();
        } catch (IOException e) {
          System.out.println("close br error:"+e);
        }
      }
    }
  }

七、成功的喜悦

1608463384219-6e39deaf-f9c9-4c19-aea5-68eec473adb6.png

可以看到我想解析的md文件的数据也都被解析出来了:

1608463434842-4d1ef5ef-0a2b-4abc-bea8-49d7941fa022.png
本站内容大部分转载于网络,并由站长进行整理后发布,文章版权属于原作者。
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
【云+社区年度征文】借鉴了Mybatis源码解决了项目上线时的一个问题
本篇文章是我对Swagger进行了二次开发,并封装成了一个框架,发布到了maven私服,这样就可以达到拿来即用啦。但是出现了一个问题,导致打包成jar包之后某些...
<<上一篇
下一篇>>