JAVA Compiler APIを利用してBeanのgetter、setterを不要にする
以前から、「javaのgetter、setterは面倒だ」と書いてきた。
2007-10-12 - ほげにっき
JDK7から1st class propertyてのがサポートされてgetter、setterが不要になるはず・・だったのだが、どうやらなくなりそうみたいでガクリorz。
まあ、個人的にはpublic fieldアクセスでいいじゃん!てなカンジなのだが、フレームワークやユーティリティによってはJava Beansのproperty仕様に依存している物が多くていろいろ不便だ。*1
てなわけで、最近ちょっと触ってみたCompiler APIを利用してpublic fieldをJava Beansのpropertyとして扱えるようにする仕組みを考えてみた。仕掛けは単純で、public fieldのみの構造体クラスを引数に渡すとgetter、setterを動的に組み込んだサブクラスをコンパイル・ロードして生成してくれる。これで、コーディングはfield直アクセスして、フレームワーク・ユーティリティーはプロパティアクセスできる。
ソース
とりあえず実験コードなので、実用には耐えられません。悪しからず。
public class DynaBeanFactory { public static <T> T create(Class<T> clazz) throws Exception { return getDynaClass(clazz).newInstance(); } public static <T> Class<? extends T> getDynaClass(Class<T> originalClass) throws Exception { String packageName = originalClass.getPackage().getName(); String simpleName = originalClass.getSimpleName() + "___DynaBean"; String fullName = packageName + "." + simpleName; StringBuilder src = new StringBuilder(); src.append("package " + packageName + ";\n" + "public class " + simpleName + " extends " + originalClass.getName() + " {\n"); for (Field field : originalClass.getFields()) { String fieldName = field.getName(); String capitalized = capitalize(fieldName); String fieldType = field.getType().getName(); src.append(" public " + fieldType + " get" + capitalized + "() {\n" + " return this." + fieldName + ";\n" + " }\n" + " public void set" + capitalized + "(" + fieldType + " val) {\n" + " this." + fieldName + "=val;\n" + " }\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 (Class<? extends T>) cl.loadClass(fullName); } finally { fileManager.close(); } } private static String capitalize(String name) { return Character.toUpperCase(name.charAt(0)) + (name.length() > 1 ? name.substring(1) : ""); } private static class StringJavaFileObject extends SimpleJavaFileObject { private String content; public StringJavaFileObject(String className, String content) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } } private static class JavaClassObject extends SimpleJavaFileObject { public JavaClassObject(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); } protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); @Override public OutputStream openOutputStream() throws IOException { return bos; } public byte[] getBytes() { return bos.toByteArray(); } private Class<?> clazz = null; public void setDefinedClass(Class<?> c) { clazz = c; } public Class<?> getDefinedClass() { return clazz; } } private static class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> { public ClassFileManager(JavaCompiler compiler, DiagnosticListener<? super JavaFileObject> listener) { super(compiler.getStandardFileManager(listener, null, null)); } private JavaClassObject jclassObject; @Override public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { jclassObject = new JavaClassObject(className, kind); return jclassObject; } protected ClassLoader loader = null; @Override public ClassLoader getClassLoader(Location location) { return new SecureClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> c = jclassObject.getDefinedClass(); if (c == null) { byte[] b = jclassObject.getBytes(); c = super.defineClass(name, b, 0, b.length); jclassObject.setDefinedClass(c); } return c; } }; } } }
テスト↓↓
package test.bean; public class TestBean { public String hoge; public String moke; }
public class DynaBeanFactoryTest extends TestCase { public void testCreate() throws Exception { TestBean bean = DynaBeanFactory.create(TestBean.class); bean.hoge = "hoge1!"; bean.moke = "moke1!"; assertEquals("hoge1!", PropertyUtils.getProperty(bean, "hoge")); assertEquals("moke1!", PropertyUtils.getProperty(bean, "moke")); PropertyUtils.setProperty(bean, "hoge", "hoge2!"); PropertyUtils.setProperty(bean, "moke", "moke2!"); assertEquals("hoge2!", bean.hoge); assertEquals("moke2!", bean.moke); } }
でけた!(^o^)/
しかし・・・最近はツールがgetter, setterを生成してくれるから・・・あんまりメリットないかな。