1

I'm using Jrebel 6.3.0 to hot reload Mybatis's Mapper XML for my Spring boot web application. And I used Java Configuration to config Mybatis.

@Configuration
@ConditionalOnClass({ PageInterceptor.class })
@EnableConfigurationProperties(MybatisPageProperties.class)
@AutoConfigureBefore(MybatisAutoConfiguration.class)
public class MyBatisPageAutoConfiguration implements ApplicationContextAware {
    private static final Logger LOG = LoggerFactory.getLogger(MyBatisPageAutoConfiguration.class);

    @Autowired
    private MybatisPageProperties properties;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtil.setApplicationContext(applicationContext);
    }

    /**
     * 
     * 
     * @return
     */
    @Bean
    public PageInterceptor pageInterceptor() {
        LOG.info("========PageInterceptor========");
        PageInterceptor pageInterceptor = new PageInterceptor();

        Properties p = new Properties();
        p.setProperty("dialect", properties.getDialect());
        p.setProperty("sqlIdRegex", properties.getSqlIdRegex());
        pageInterceptor.setProperties(p);

        return pageInterceptor;
    }

I found that Jrebel clear the interceptors when reload the mapper xml file.

org.zeroturnaround.jrebel.mybatis.SqlMapReloader

  private void reconfigure() {
    log.infoEcho("Reloading SQL maps");

    this.conf.reinit(); // Clear loadedResources and interceptors

    reloadedResources.set(Collections.synchronizedSet(new HashSet()));
    enterReloading();
    try {
      if (this.confBuilder != null)
        this.confBuilder.reinit();
      reconfigureAdditionalMappings();


      exitReloading();
      reloadedResources.remove();
    }
    finally
    {
      exitReloading();
      reloadedResources.remove();
    }
  }

  .........

  private void reconfigureAdditionalMappings() {
    for (ResourceDesc rd : (ResourceDesc[])this.additionalMappings.toArray(new ResourceDesc[0])) {
      reconfigureMapping(rd);
    }
  }

  private void reconfigureMapping(ResourceDesc rd) {
    org.apache.ibatis.session.Configuration c = (org.apache.ibatis.session.Configuration)this.conf;
    try { // Only reload loadedResources from Mapper XML files.
      XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(ResourceUtil.asInputStream(rd.url), c, rd.path, c.getSqlFragments());

      xmlMapperBuilder.parse();
    }
    catch (Exception e) {
      if ((e.getCause() instanceof java.io.FileNotFoundException)) removeMappingForDeletedResource(rd); else {
        throw new RuntimeException("Failed to parse mapping resource: '" + rd.url + "'", e);
      }
    } finally {
      ErrorContext.instance().reset();
    }
  }

org.zeroturnaround.jrebel.mybatis.cbp.ConfigurationCBP

public class ConfigurationCBP
  extends JavassistClassBytecodeProcessor
{
  public void process(ClassPool cp, ClassLoader cl, CtClass ctClass)
    throws Exception
  {
    ctClass.addInterface(cp.get(JrConfiguration.class.getName()));

    ctClass.addField(new CtField(cp.get(SqlMapReloader.class.getName()), "reloader", ctClass));

    CtConstructor[] constructors = ctClass.getConstructors();
    for (int i = 0; i < constructors.length; i++) {
      CtConstructor constructor = constructors[i];
      if (constructor.callsSuper()) {
        constructor.insertAfter("reloader = new " + SqlMapReloader.class.getName() + "($0);");
      }
    }

    ctClass.addMethod(CtNewMethod.make("public " + SqlMapReloader.class
      .getName() + " getReloader() {" + "  return reloader;" + "}", ctClass));




    ctClass.addMethod(CtNewMethod.make("public void reinit() {  loadedResources.clear();  ((" + JrInterceptorChain.class


      .getName() + ") interceptorChain).jrClear();" + "}", ctClass));



    ctClass.getDeclaredMethod("isResourceLoaded").insertAfter("if (reloader.doReload($1)) {  loadedResources.remove($1);  $_ = false;}");
  }
}

Is there anyway to let Jrebel to keep interceptors?

John.Yuan
  • 11
  • 5
  • Have you found any resolution to your problem? – patryk Aug 25 '16 at 07:49
  • 1
    I did not modify jrebel, just modify one class in mybatis. org.apache.ibatis.builder.xml.XMLMapperBuilder. At the end of method public void parse() { add the following code: // Added by yuanjinyong if (interceptors.size() == 0) { interceptors.addAll(configuration.getInterceptors()); } if (configuration.getInterceptors().size() == 0) { for (Interceptor interceptor : interceptors) { configuration.addInterceptor(interceptor); } } – John.Yuan Aug 28 '16 at 08:18
  • Did you modify the class itself? Or maybe there is a way to create a new class that extends XMLMapperBuilder in your code and somehow replace the one that mybatis uses with the one you wrote? – patryk Aug 29 '16 at 07:48
  • My project is web app, We don't need this change in prod-env, we just use jrebel in dev-env, so just modify the source file which fetch form the source jar, and place it in src/test/java, and it will be build to classes dir, and replace the class in original jar. Don't place it in to src/main/java folder. – John.Yuan Aug 31 '16 at 00:51

1 Answers1

0

My project is web app, We don't need this change in prod-env, we just use jrebel in dev-env, so just modify the source file which fetch form the source jar, and place it in src/test/java, and it will be build to classes dir, and replace the class in original jar. Don't place it in to src/main/java folder.

org.apache.ibatis.builder.xml.XMLMapperBuilder

...
public class XMLMapperBuilder extends BaseBuilder {
    private static List<Interceptor> interceptors = new ArrayList<Interceptor>(); // Added by yuanjinyong
...
    public void parse() {
...
        parsePendingStatements();

        // Added by yuanjinyong
        if (interceptors.size() == 0) {
            interceptors.addAll(configuration.getInterceptors());
        }
        if (configuration.getInterceptors().size() == 0) {
            for (Interceptor interceptor : interceptors) {
                configuration.addInterceptor(interceptor);
            }
        }
    }
...
}
John.Yuan
  • 11
  • 5