ほげにっき

hogedigoの日記

JAVA Compiler APIを利用して高速にプロパティアクセスする

JAVA Compiler APIをつかってなんか出来ないかなシリーズ第2弾!
といっても第1弾より先に試したものだけども。。メモ代わりにソース載っけます。

ちなみに第1弾はこちら↓
JAVA Compiler APIを利用してBeanのgetter、setterを不要にする - ほげにっき

概要

Java Beansのプロパティにリフレクション(イントロスペクション?)経由でアクセスすると結構に遅い。そこで、直接getter, setterを呼び出すコードを動的に生成してアクセスするだす。

ソース

例によって実験用なので実用には耐えられません。悪しからず。

public class FastPropertyUtils {
    
  public static PropertyAccessor getPropertyAccesor(Class<?> originalClass) throws Exception {

    String packageName = originalClass.getPackage().getName();
    String simpleName = originalClass.getSimpleName() + "___PropertyAccessor";
    String fullName = packageName + "." + simpleName;
    
    StringBuilder src = new StringBuilder();
    src.append("package " + packageName + ";\n" + "public class "
        + simpleName + " implements " + PropertyAccessor.class.getName() + " {\n");

    BeanInfo beanInfo = Introspector.getBeanInfo(originalClass);
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    
    
    src.append("  public void set(Object bean, String property, Object value) {").append("\n");
    src.append("  "+originalClass.getName()+" casted = ("+originalClass.getName()+")bean;").append("\n");
    boolean first = true;
    for (PropertyDescriptor pd : propertyDescriptors) {
      if (pd.getWriteMethod() != null) {             
        String propName = pd.getName();
        String propType = 
          pd.getPropertyType() == int.class ? // toriaezu int
              Integer.class.getName() : pd.getPropertyType().getName();
        String setterName = pd.getWriteMethod().getName();
        
        src.append("  " + (first ? "" : "else ") + "if (property.equals(\""+propName+"\")) {").append("\n");
        src.append("    casted."+setterName+"(("+propType+")value);").append("\n");
        src.append("  }").append("\n");
        
        first = false;
      }
    }
    src.append("  else {").append("\n");
    src.append("    throw new IllegalArgumentException();").append("\n");
    src.append("  }").append("\n");    
    src.append("  }").append("\n");

    src.append("  public Object get(Object bean, String property) {").append("\n");
    src.append("  "+originalClass.getName()+" casted = ("+originalClass.getName()+")bean;").append("\n");
    first = true;
    for (PropertyDescriptor pd : propertyDescriptors) {
      if (pd.getReadMethod() != null && !pd.getName().equals("class")) {             
        String propName = pd.getName();
        String propType = pd.getPropertyType().getName();
        String getterName = pd.getReadMethod().getName();
        
        src.append("  " + (first ? "" : "else ") + "if (property.equals(\""+propName+"\")) {").append("\n");
        src.append("    return ("+propType+")casted."+getterName+"();").append("\n");
        src.append("  }").append("\n");
        
        first = false;
      }
    }
    src.append("  else {").append("\n");
    src.append("    throw new IllegalArgumentException();").append("\n");
    src.append("  }").append("\n");    
    src.append("  }").append("\n");

    src.append("}\n");

    System.out.println(src);
    
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>();
    JavaFileManager fileManager = new ClassFileManager(compiler, collector);

    try {

      List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
      jfiles.add(new StringJavaFileObject(fullName, src.toString()));

      JavaCompiler.CompilationTask task = compiler.getTask(null,
          fileManager, collector, null, null, jfiles);

      if (!task.call()) {
        throw new IllegalStateException("Error!");
      }

      ClassLoader cl = fileManager.getClassLoader(null);
      return (PropertyAccessor) cl.loadClass(fullName).newInstance();
    } finally {
      fileManager.close();
    }
  }
}

前回インナークラスとして使ってたいくつかのユーティリティクラスはtop levelに移動して使い回した。
↓参照↓
http://d.hatena.ne.jp/digo/20090610#1244643301

テスト

  public void testSetAndGet() throws Exception {
    TestBean2 bean = new TestBean2();
    
    PropertyAccessor accessor =
      FastPropertyUtils.getPropertyAccesor(TestBean2.class);
    
    accessor.set(bean, "hoge", "hogetest!");
    
    assertEquals("hogetest!", bean.getHoge());
    assertEquals("hogetest!", accessor.get(bean, "hoge"));
      

    accessor.set(bean, "moke", 999);
    
    assertEquals(999, bean.getMoke());
    assertEquals((Integer)999, (Integer)accessor.get(bean, "moke"));
  }

でけた!(^o^)/ ちゃんとget, setできてます。
で速度は・・超適当計測だけど↓な結果に

FastPropertyUtils 14941564(nanosec)
commons PropertyUtils 1105767074(nanosec)

※get,setを100000回loop


こんなに効果が!!



・・・と言いたいところだけど、前にも言った通り実はcglibのFastClassとあまり速度変わらない*1。FastClass使えるならそれを使おう。


今携わっているProjectはOSS禁止なのでもしかしたら日の目をみることもあるかもしれない。。


それにしてもcompiler APIなかなかオモロイね。なんか他にも出来そうだ。

*1:前回はFastClassより遅いと言ってましたがそれは計測ミスで、実質ほぼ同速だった