zoukankan      html  css  js  c++  java
  • 基于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:jrebelpluginjr-mybatisplus.jar

    打开你的IDE(Intellij IDEA or Eclipse),修改运行配置,增加VM参数:-Drebel.plugins=d:jrebelpluginjr-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热加载插件/

  • 相关阅读:
    test
    4css
    3css
    2css
    5html
    1css
    4html
    3html
    2html
    1.3 tensorflow2.0 常用函数
  • 原文地址:https://www.cnblogs.com/Jimc/p/11101895.html
Copyright © 2011-2022 走看看