読者です 読者をやめる 読者になる 読者になる

Java6と7でPropertyDescriptorの動作が微妙に違う互換性バグ

Java

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感が目立つ。