更新:Oracleはこれをバグとして確認しました。
概要:JDK1.6で機能する特定のカスタムBeanInfo
とPropertyDescriptor
はJDK1.7で失敗し、一部はガベージコレクションが実行されて特定のSoftReferenceをクリアした後にのみ失敗します。
編集:これは
ExtendedBeanInfo
、投稿の下部に記載されているように、Spring3.1でも機能しなくなります。編集:JavaBeans仕様のセクション7.1または8.3を呼び出す場合は、仕様のそれらの部分が何かを必要とする場所を正確に説明してください。これらのセクションでは、言語は必須でも規範的でもありません。これらのセクションの言語は例の言語であり、仕様としてはせいぜいあいまいです。さらに、
BeanInfo
APIを使用すると、デフォルトの動作を具体的に変更できます。これは、以下の2番目の例では明らかに壊れています。
Java Beans仕様では、return型がvoidのデフォルトのsetterメソッドを探しますが、。を使用してgetterメソッドとsetterメソッドをカスタマイズできますjava.beans.PropertyDescriptor
。これを使用する最も簡単な方法は、ゲッターとセッターの名前を指定することです。
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
これは、JDK1.5およびJDK1.6で機能し、以下のテストケースのように、戻りタイプがvoidでない場合でも、セッター名を指定します。
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import org.testng.annotations.*;
/**
* Shows what has worked up until JDK 1.7.
*/
public class PropertyDescriptorTest
{
private int i;
public int getI() { return i; }
// A setter that my people call "fluent".
public PropertyDescriptorTest setI(final int i) { this.i = i; return this; }
@Test
public void fluentBeans() throws IntrospectionException
{
// This throws an exception only in JDK 1.7.
final PropertyDescriptor pd = new PropertyDescriptor("i",
PropertyDescriptorTest.class, "getI", "setI");
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
}
BeanInfo
Java Beans仕様のsをプログラムで制御できるカスタムsの例はPropertyDescriptor
すべて、セッターにvoid戻り型を使用しますが、仕様にはこれらの例が規範的であることを示すものはなく、この低レベルユーティリティの動作には次のようなものがあります。新しいJavaクラスで変更されました。これにより、作業中のコードが壊れてしまいました。
JDK 1.6と1.7の間のパッケージには多くの変更がありますがjava.beans
、このテストが失敗する原因は次の差分にあるようです。
@@ -240,11 +289,16 @@
}
if (writeMethodName == null) {
- writeMethodName = "set" + getBaseName();
+ writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
- writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
- (type == null) ? null : new Class[] { type });
+ Class[] args = (type == null) ? null : new Class[] { type };
+ writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
+ if (writeMethod != null) {
+ if (!writeMethod.getReturnType().equals(void.class)) {
+ writeMethod = null;
+ }
+ }
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
正しい名前とパラメーターを持つメソッドを単に受け入れる代わりに、PropertyDescriptor
は戻り型をチェックしてnullかどうかを確認するようになったため、流暢なセッターは使用されなくなりました。この場合PropertyDescriptor
、スローはIntrospectionException
「メソッドが見つかりません:setI」です。
ただし、この問題は、上記の単純なテストよりもはるかに潜行的です。PropertyDescriptor
カスタムのでgetterメソッドとsetterメソッドを指定する別の方法はBeanInfo
、実際のMethod
オブジェクトを使用することです。
@Test
public void fluentBeansByMethod()
throws IntrospectionException, NoSuchMethodException
{
final Method readMethod = PropertyDescriptorTest.class.getMethod("getI");
final Method writeMethod = PropertyDescriptorTest.class.getMethod("setI",
Integer.TYPE);
final PropertyDescriptor pd = new PropertyDescriptor("i", readMethod,
writeMethod);
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
上記のコードは1.6と1.7の両方で単体テストに合格しますが、最初の例がすぐに失敗するのとまったく同じ変更により、JVMインスタンスの存続期間中のある時点でコードが失敗し始めます。2番目の例では、カスタムを使用しようとしたときに、問題が発生したことを示す唯一の兆候がありますPropertyDescriptor
。セッターはnullであり、ほとんどのユーティリティコードは、プロパティが読み取り専用であることを意味します。
差分のコードはの中にありPropertyDescriptor.getWriteMethod()
ます。SoftReference
実際のセッターの保持Method
が空のときに実行されます。このコードは、上記PropertyDescriptor
のアクセサーメソッド名を使用する最初の例のコンストラクターによって呼び出されます。これは、最初は実際のゲッターとセッターを保持するsMethod
に保存されていないためです。SoftReference
2番目の例では、読み取りメソッドと書き込みメソッドはコンストラクターによってSoftReference
オブジェクトに格納されPropertyDescriptor
、最初はコンストラクターに指定されたゲッターとセッターreadMethod
への参照が含まれます。ある時点で、ガベージコレクターに許可されている(そして実行できる)ときにこれらのソフト参照がクリアされると、コードは、がnullを返すことを確認し、セッターを検出しようとします。今回は、JDK 1.7で最初の例が失敗する原因となる内部の同じコードパスを使用すると、戻りタイプがではないため、書き込みがに設定されます。(戻り型はJavaメソッドのシグニチャーの一部ではありません。)writeMethod
Method
getWriteMethod()
SoftReference
PropertyDescriptor
Method
null
void
カスタムを使用するときにこのような動作が時間の経過とともに変化BeanInfo
すると、非常に混乱する可能性があります。ガベージコレクターがそれらの特定のものをクリアする原因となる条件を複製しようとすることSoftReferences
も退屈です(ただし、いくつかのインストルメンテーションモックが役立つ場合があります)。
SpringExtendedBeanInfo
クラスには、上記と同様のテストがあります。これは、ユニットテストモードで合格する実際のSpring 3.1.1ユニットテストExtendedBeanInfoTest
ですが、テスト対象のコードは、GC後のインシディアスモードでは失敗します。
@Test
public void nonStandardWriteMethodOnly() throws IntrospectionException {
@SuppressWarnings("unused") class C {
public C setFoo(String foo) { return this; }
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
}
1つの提案は、setterメソッドがソフトにしか到達できないようにすることで、現在のコードを非voidセッターで機能させ続けることができるということです。それはうまくいくように見えますが、それはJDK1.7で変更された動作を回避するためのハックです。
Q:非ボイドセッターはアナテマであるべきだという明確な仕様はありますか?何も見つかりませんでした。現在、これはJDK1.7ライブラリのバグだと考えています。私は間違っていますか、そしてなぜですか?