hakobera's blog

技術メモ。たまに雑談

Spring で Hotdeploy その5

本命のキャッシュ無効化!
org.zeroturnaround.javarebel.integration.spring.InjectionMetadataCacheCBP を移植?します。

public class InjectionMetadataCacheCBP extends JavassistClassBytecodeProcessor {

  public void process(ClassPool cp, ClassLoader cl, CtClass ctClass) throws Exception {
    CtConstructor[] ctrs = ctClass.getConstructors();
    for (int i = 0; i < ctrs.length; i++) {
      ctrs[i].insertAfter(
          "injectionMetadataCache = new org.zeroturnaround.javarebel.integration.spring.NopMap();");      
    }
  }
}

このコード内部的に private な HashMap で保持してるキャッシュ を何もしない NopMap に 書き換えてます。private 変数をいじくりたいので、まともな方法だと手がでません。よって、あまりきれいではありませんが、リフレクションを利用して強引にprivate 変数にアクセスします。

キャッシュの無効化の対象は、以下の3つのクラスです。

  • org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
  • org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor

全部、BeanPostProcessor インターフェース実装クラスなので、XmlWebApplicationContext#registerBeanPostProcessors メソッドをオーバーライドすることで対応します。

実際のコードは次のような感じです。一部、説明していないクラスとかがありますが、雰囲気をつかんでいただければいいかと。

public class ReloadableXmlWebApplicationContext extends XmlWebApplicationContext {

// (略)

  /**
   * BeanPostProcessorの登録をおこなうと同時に、
   * Hotdeployのために内部キャッシュを無効に設定します。
   * @param beanFactory {@link BeanPostProcessor} を登録する {@link BeanFactory}
   * @see AutowiredAnnotationBeanPostProcessor
   * @see CommonAnnotationBeanPostProcessor
   * @see PersistenceAnnotationBeanPostProcessor
   */
  protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    super.registerBeanPostProcessors(beanFactory);
		
    if (DeploymentMode.isDevelopmentMode()) {
      AbstractAutowireCapableBeanFactory factory = null;
      if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
	factory = (AbstractAutowireCapableBeanFactory) beanFactory;
      }
			
      if (factory != null) {
	Iterator it = factory.getBeanPostProcessors().iterator();
	while (it.hasNext()) {
	  BeanPostProcessor processor = (BeanPostProcessor) it.next();
	  if (processor instanceof AutowiredAnnotationBeanPostProcessor) {
	    disableCache(AutowiredAnnotationBeanPostProcessor.class, processor, "candidateConstructorsCache");
	    disableCache(AutowiredAnnotationBeanPostProcessor.class, processor, "injectionMetadataCache");
	  } else if (processor instanceof CommonAnnotationBeanPostProcessor) {
	    disableCache(CommonAnnotationBeanPostProcessor.class, processor, "injectionMetadataCache");
	  } else if (processor instanceof PersistenceAnnotationBeanPostProcessor) {
	    disableCache(PersistenceAnnotationBeanPostProcessor.class, processor, "injectionMetadataCache");
	  }
        }
      }
    }
  }

  /**
   * Reflectionを利用して、内部キャッシュを保持しているMap型の
   * privateフィールドに対して、{@link NoOperationMap}を設定します。 
   * @param klass 設定対象クラス
   * @param target 設定対象インスタンス
   * @param fieldName 設定対象フィールド
   */
  private void disableCache(Class klass, Object target, String fieldName) {
    Field cache = ReflectionUtils.findField(klass, fieldName);
    if (cache != null) {
      ReflectionUtils.makeAccessible(cache);
      ReflectionUtils.setField(cache, target, new NoOperationMap());
    }
  }
}

ちなみにSpringでHotdeployの解説が終わり次第、まとめてどっかに公開する予定です。