Java6と7でPropertyDescriptorの動作が微妙に違う互換性バグ
PropertyDescriptorはJavaBeansのsetter/getterメソッドを扱うクラスです。
PropertyDescriptor (Java Platform SE 6)
使い方については以下を参照。
Javaリフレクションメモ(Hishidama's Java Reflection Memo)
一言で言うと、JavaのBeanクラス(に限らず、POJOクラス全般)に対して、あるプロパティに対応するsetter/getterのペアを扱うためのクラス。しかし、どうもJava6までとJava7以降だと動作が微妙に違うっぽい。
具体的には、setterとして指定可能なメソッドの条件が違う。Java6だとsetterの戻り値がvoidだろうが具体的に何かを戻そうが関係無くPropertyDescriptorでsetterとして指定可能だった。
しかし、Java7(手持ちのjdk 1.7.0_4と1.7.0_17で確認)では戻り値がvoidのメソッドしかsetterとして指定できない。
サンプルコード。以下のコードはjava6環境でもjava7環境でも問題なくテストが通る。
package example; import static org.junit.Assert.fail; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import org.junit.Test; public class PropertyDescriptorTest1 { private class Person{ private String name; public Person(String name){ this.name = name; } public String getName(){ return this.name; } //setterの戻り値がvoid public void setName(String name){ this.name = name; } } @Test public void testPropertyDescriptor1_1(){ System.out.println("exec test. java version: " + System.getProperty("java.version")); try { //property名を基にPropertyDescriptorを作成 PropertyDescriptor pd = new PropertyDescriptor("name", Person.class); } catch (IntrospectionException e) { e.printStackTrace(); fail("IntrospectionException is thrown!"); } } @Test public void testPropertyDescriptor1_2(){ System.out.println("exec test. java version: " + System.getProperty("java.version")); try { //アクセスメソッド名を明示してPropertyDescriptorを作成 //第三引数がgetter、第四引数がsetterメソッド名 PropertyDescriptor pd = new PropertyDescriptor("name", Person.class, "getName", "setName"); } catch (IntrospectionException e) { e.printStackTrace(); fail("IntrospectionException is thrown!"); } } }
しかし、以下のコード(setterがvoidではなく戻り値を戻す)はJava6環境では問題なく動作するものの、Java7環境ではPropertyDescriptorインスタンスをnewしたときにIntrospectionExceptionがスローされる。
package example; import static org.junit.Assert.fail; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import org.junit.Test; public class PropertyDescriptorTest2 { private class Person{ private String name; public Person(String name){ this.name = name; } public String getName(){ return this.name; } //流れるようなインターフェイス! public Person setName(String name){ this.name = name; return this; } } @Test public void testPropertyDescriptor2_1(){ System.out.println("exec test. java version: " + System.getProperty("java.version")); try { //property名を基にPropertyDescriptorを作成 PropertyDescriptor pd = new PropertyDescriptor("name", Person.class); } catch (IntrospectionException e) { e.printStackTrace(); fail("IntrospectionException is thrown!"); } } @Test public void testPropertyDescriptor2_2(){ System.out.println("exec test. java version: " + System.getProperty("java.version")); try { //アクセスメソッド名を明示してPropertyDescriptorを作成 //第三引数がgetter、第四引数がsetterメソッド名 PropertyDescriptor pd = new PropertyDescriptor("name", Person.class, "getName", "setName"); } catch (IntrospectionException e) { e.printStackTrace(); fail("IntrospectionException is thrown!"); } } }
これで何が困るかというと、たとえばアノテーションを使用してpropertyに印をつけ、PropertyDescriptorでsetter/getterを取得するようなコードで、かつ流れるようなインターフェイスを使用しているとjava7で動かなくなる可能性がある(かと言ってリフレクション使ってprivateなプロパティにアクセスするとSecurityManagerが有効な環境で詰むしなあ)。
さて、この非互換動作が仕様なのかバグなのか調べてみると、以下のバグレポートに行き当たった。
Bug ID: 7172865 PropertyDescriptor fails to work with setter method name if setter is non-void(未だにbugs.sun.comなのね・・・)
どうも他のバグ(6723447)を潰した副作用で発生した退行バグらしい。てかTargeted Versionsが8になってるし。結構影響範囲が大きそうだけどそんな呑気で大丈夫なのかなあ。
しかしjava7になってからgdgd感が目立つ。