基于JRebel开发的MybatisPlus热加载插件

前言

前天项目中使用了mybatis-plus,但是搭配Jrebel开发项目时,发现修改mapper的xml,或者mapper方法中的注解,Jrebel并没有能够reload mapper.于是就有了本篇文章

探索

为了解决这个问题,首先想到的是到mybatis-plus官网查看配置方法,官网中的文档热加载很清楚说明了

3.0.6版本上移除了该功能,不过最新快照版已加回来并打上废弃标识,3.1.0版本上已完全移除

按照官网配置

@Bean
@Profile("dev") // 
public MybatisMapperRefresh mybatisMapperRefresh (MybatisPlusProperties properties, SqlSessionFactory sessionFactory){
    return new MybatisMapperRefresh(properties.resolveMapperLocations(), sessionFactory, true);
}

上述配置后重新运行项目,修改mapper,发现并没有生效,于是开始研究他的源码。
通过查看MybatisMapperRefresh源码发现他的实现方式:重建mapper来实现热加载的。

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
             sqlSessionFactory.getConfiguration(),
             resource.toString(), sqlSessionFactory.getConfiguration().getSqlFragments());
         xmlMapperBuilder.parse();

最终定位到关键代码

/**
   * MybatisPlus 加载 SQL 顺序:
   * <p>1、加载XML中的SQL</p>
   * <p>2、加载sqlProvider中的SQL</p>
   * <p>3、xmlSql 与 sqlProvider不能包含相同的SQL</p>
   * <p>调整后的SQL优先级:xmlSql > sqlProvider > curdSql</p>
   */
  @Override
  public void addMappedStatement(MappedStatement ms) {
      logger.debug("addMappedStatement: " + ms.getId());
      if (mappedStatements.containsKey(ms.getId())) {
          /*
           * 说明已加载了xml中的节点; 忽略mapper中的SqlProvider数据
           */
          logger.error("mapper[" + ms.getId() + "] is ignored, because it exists, maybe from xml file");
          return;
      }
      super.addMappedStatement(ms);
  }

注:Mybatisplus重写了mybatis的Configuration类(这也是Jrebel 无法热加载SQL maps)的原因之一

上面添加MappedStatementConfiguration时先判断了是否已经加载过,但是项目启动时,Configuration已加载了所有的MappedStatement,所以MybatisMapperRefresh 这个后台线程后面reload完全没有作用.

开搞

为了弄清楚JRebel是如何实现mybatis热加载。

我把jrebel的插件作为libary,添加到工程里.

利用IDEA 天然的反编译功能,顺利成章的看到了源码
下面是mybatis热加载插件的主入口

public void preinit() {
    ClassLoader cl = MyBatisPlugin.class.getClassLoader();
    Integration integration = IntegrationFactory.getInstance();
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.io.Resources", new ResourcesCBP());
    integration.addIntegrationProcessor(cl, new String[]{"org.apache.ibatis.builder.xml.XMLConfigBuilder", "pl.atena.ibatisbaf.core.config.ConfigBuilder"}, new XMLConfigBuilderCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.session.defaults.DefaultSqlSessionFactory", new DefaultSqlSessionFactoryCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.session.Configuration", new ConfigurationCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.session.Configuration$StrictMap", new StrictMapCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.binding.MapperRegistry", new MapperRegistryCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.reflection.Reflector", new ReflectorCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.reflection.DefaultReflectorFactory", new DefaultReflectorFactoryCBP());
    integration.addIntegrationProcessor(cl, "org.mybatis.spring.SqlSessionFactoryBean", new SqlSessionFactoryBeanCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.builder.annotation.MapperAnnotationBuilder", new MapperAnnotationBuilderCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.type.TypeAliasRegistry", new TypeAliasRegistryCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.plugin.InterceptorChain", new InterceptorChainCBP());
    integration.addIntegrationProcessor(cl, "org.mybatis.spring.mapper.MapperFactoryBean", new MapperFactoryBeanCBP());
    integration.addIntegrationProcessor(cl, "org.mybatis.spring.annotation.MapperScannerRegistrar", new MapperScannerRegistrarCBP());
    integration.addIntegrationProcessor(cl, "org.apache.ibatis.binding.MapperProxy", new MapperProxyCBP());
}

JRebel在应用启动时对mybatis的某些类做了Hook(利用Javaassit)

上述的类都是和Mapper相关的(Mapper文件解析,Mapper 注解解析…)

由于mybatis-plus重写了mybatis的一些核心类(而JRebel的插件对mybatis中的关键类做了HOOK),所以导致项目中整合mybatis-plus时,修改了mapper没有被热加载.

为了使mybatis-plus也能够热加载,我想到了hook Mybatis-plus中的关键类,于是阅读了mybatis-plus的源码,整理出如下mp重写的mybatis类。

  • MybatisConfiguration.java
  • MybatisMapperAnnotationBuilder.java
  • MybatisSqlSessionFactoryBean.java
  • MybatisMapperProxy.java

然后趁IDEA不注意的时候,去Jrebel的官网找到了开发自定义插件的文档Custom JRebel plugins.
最终写了这个插件。

PS:其中大部分的代码来自原插件反编译后代码,同时结合Mybatis-plus重写的源码,做了相应适配.

下面是插件源码地址:

jrebel-mybatisplus

下面是插件下载地址:

jr-mybatisplus.zip

如何使用请阅读README.md

使用

将下载好的插件jr-mybatisplus.zip解压并拷贝至任意目录, 比如: d:\jrebel\plugin\jr-mybatisplus.jar

打开你的IDE(Intellij IDEA or Eclipse),修改运行配置,增加VM参数:-Drebel.plugins=d:\jrebel\plugin\jr-mybatisplus.jar,然后以JRebel方式启动

检查插件是否生效:

修改你项目中的mapper xml 文件后,重新编译,如果重新请求接口,你应该会看到控制台输出 “Reloading SQL maps”

总结

    • 学习了javaassit的使用.

    • 阅读了mybatis && mybatis-plus 的源码,了解到mybatis的实现原理,以及mybatis-plus的相关原理.

    • 学习了JRebel的插件开发方式以及它的Hot Reloading原理.

 文章来源:https://githuboy.online/2019/05/11/基于JRebel开发的MybatisPlus热加载插件/

posted @ 2019-06-28 11:49  寒爵  阅读(4624)  评论(5编辑  收藏  举报