更新:Oracle已经确认这是一个bug。
Java Beans规范查找返回类型为void的默认setter方法,但它允许通过Java.Beans.PropertyDescriptor
定制getter和setter方法。最简单的使用方法是指定getter和setter的名称。
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
这在JDK1.5和JDK1.6中起到了作用,即使它的返回类型不是void,也可以指定setter名称,如下面的测试用例所示:
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
的示例允许对propertydescriptor
的编程控制,在Java Beans规范中,这些示例都使用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,因此不再使用fluent setter。PropertyDescriptor
抛出InspectionException
在本例中:“method not found:seti”。
但是,问题比上面的简单测试要阴险得多。为自定义BeanInfo
在PropertyDescriptor
中指定getter和setter方法的另一种方法是使用实际的方法
对象:
@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实例的生命周期中的某个时间点,代码将开始失败,原因是导致第一个示例立即失败的相同更改。在第二个示例中,当尝试使用自定义的PropertyDescriptor
时,才会出现任何错误。setter为null,大多数实用程序代码认为这意味着属性是只读的。
diff中的代码在PropertyDescriptor.getWriteMethod()
中。它在保存实际setter方法
的softreference
为空时执行。在第一个示例中,PropertyDescriptor
构造函数调用了此代码,该构造函数采用了上面的访问器方法名称,因为最初在保存实际getter和setter的SoftReference
的SoftReference
中没有保存方法
。
在第二个示例中,read方法和write方法由构造函数存储在PropertyDescriptor
中的SoftReference
对象中,首先这些对象将包含对给构造函数的ReadMethod
和WriteMethod
getter和setter方法
的引用。如果在某一点上,这些软引用被清除,因为垃圾收集器被允许这样做(垃圾收集器也会这样做),那么GetWriteMethod()
代码将看到SoftReference
返回null,并且它将尝试发现setter。这一次,使用导致JDK 1.7中第一个示例失败的PropertyDescriptor
内的相同代码路径,它将write方法
设置为null
,因为返回类型不是void
。(返回类型不是Java方法签名的一部分。)
在使用自定义beaninfo
时,随着时间的推移使行为发生这样的变化可能会非常混乱。试图复制导致垃圾回收器清除那些特定softreferences
的条件也是很繁琐的(尽管一些工具化的嘲弄可能会有帮助)
SpringExtendedBeanInfo
类具有与上述类似的测试。下面是来自ExtendedBeanInfoTest
的实际Spring 3.1.1单元测试,它将在单元测试模式下通过,但正在测试的代码将在后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));
}
一个建议是,我们可以通过防止setter方法仅可软访问来保持当前代码与非void setter一起工作。这看起来是可行的,但这是对JDK1.7中更改的行为的一次攻击。
问:是否有一些明确的规范说明非空设置器应该是可憎的?我没有发现任何东西,目前我认为这是JDK1.7库中的一个bug。我错了吗?为什么?
看起来规范没有改变(它需要void setter),但是实现已经更新为只允许void setter。
规格:
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html
请参阅此stackoverflow问题后面的一些答案:
Java bean的setter允许返回这个吗?
问题内容: 在我的Web应用程序中,我在Apache Tomcat(TomEE)/7.0.37服务器上使用OpenJPA。我使用Netbeans自动生成类(“来自数据库的实体类…”和“来自实体类的会话Bean …”)。在SessionBean(例如UserFacade)上,我想获取EntityManager: 但是当我通过上述方式得到它时,我得到的是空值。当我通过: ecm不为空,还可以 我的pe
问题内容: 我有Ubuntu 10.10和apache2,php 5.3.3-1和mysql 5.1。 我正在通过URL向页面传递一些值。在该页面上,如果我这样做了,那么我会看到数组的内容。但是,如果我这样做数组是空的。任何想法为什么会这样? 问题答案: 也可以尝试检查php.ini中的“ request_order” 选项:
我的问题是无法使用和获取文本字段。我尝试使用XPath来选择对象,但没有成功。 这是我的代码: 我重新提出这个问题是因为旧的问题有点让人困惑,我想。这是旧的 XML形式的我的文档(Document.getXML()) 我需要选择文本字段,做一个邮件合并,我的计划将是复制和移动字段。如果有更好的方法,我愿意尝试一下:)
问题内容: 我知道在某些版本中,Hibernate异常已更改为未经检查。是什么原因?这是哲学问题还是实际问题? 问题答案: 实际的。因此,您不必将有关Hibernate的每一项操作都包装在try catch块中。 摘自Hibernate的Java Persistence: 异常的历史-异常及其应如何处理始终以Java开发人员之间的激烈辩论而告终。hibernate也具有一些值得注意的历史也就不足为
问题内容: var x int done := false go func() { x = f(…); done = true } while done == false { } 这是Go代码。我的恶魔告诉我,这是UB代码。为什么? 问题答案: Go Memory Model不保证该程序将始终遵守在goroutine中写入x的值。go常规销毁 部分中提供了一个类似的错误程序作为示例。 在本节中,G
问题内容: 我有一些调用的代码。 不过,这将返回null。 当我从命令行而不是从Eclipse启动相同的代码时,它将返回一个类加载器。 我可以破解代码来做到这一点… 两者都被编译并使用相同的JVM运行。(我确定99.99%)。 任何人都知道为什么第一个会为类加载器返回null? 编辑: 我的问题是:“没有人知道为什么同一类通过Eclipse启动时会返回null,而从命令行加载时会返回类加载器。”