JRE 1.8 是否仍然是 JavaBean 规范抱怨 Indexed PropertyDescriptor?
Is JRE 1.8 still JavaBean specs compliant about IndexedPropertyDescriptor?
这个问题看起来很尴尬,但我们在检索 javabean 的 PropertyDescriptors 时遇到了一个奇怪的行为。
以下是一段简单代码在 1.6、1.7 和 1.8 上的执行结果,编译时符合 1.6 标准。
Java 1.6执行:
java.beans.PropertyDescriptor@4ddc1428 <- 不重要
java.beans.IndexedPropertyDescriptor@7174807e <- 是的,我有一个索引 属性
Java 1.7 执行:
java.beans.PropertyDescriptor[姓名=class; 属性类型=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- 不重要
java.beans.IndexedPropertyDescriptor[名称=值; indexedPropertyType=class java.lang.String; indexedReadMethod=public java.lang.String JavaBean.getValues(int)] <- 是的,我有一个索引 属性
Java 1.8执行:
java.beans.PropertyDescriptor[姓名=class; 属性类型=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- 不重要
java.beans.PropertyDescriptor[名称=值; 属性类型=接口java.util.List; readMethod=public java.util.List JavaBean.getValues()] <- 哎哟!这不再是索引 属性!
为什么改变了?
Javabean 规范说明了如何使用索引访问 属性。并没有说必须使用数组作为索引 属性 的容器。我错了吗?
我阅读了规范,第 8.3.3 章讨论了索引属性的设计模式,而不是严格的规则。
如何在不重构所有应用程序的情况下使以前的行为再次出现? < 旧应用程序,需要修改大量代码等...
感谢您的回答,
Java豆子class
import java.util.ArrayList;
import java.util.List;
public class JavaBean {
private List<String> values = new ArrayList<String>();
public String getValues(int index) {
return this.values.get(index);
}
public List<String> getValues() {
return this.values;
}
}
主要class
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class Test {
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor[] descs =
Introspector.getBeanInfo(JavaBean.class).getPropertyDescriptors();
for (PropertyDescriptor pd : descs) {
System.out.println(pd);
}
}
}
来自 JavaBeans 1.01 规范,第 7.2 节“索引属性”:
A component may also expose an indexed property as a single array value.
第 8.3 节描述了 introspection 在没有显式 BeanInfo 的情况下识别的设计模式。第 8.3.3 节说只有数组属性会触发索引属性的自动识别。
你在技术上是正确的;使用数组不是强制性的。但是如果你不这样做,规范说你必须提供你自己的 BeanInfo 来将 属性 公开为索引 属性.
所以您的问题标题的答案是:是的,Java 1.8 符合 JavaBean 规范。
我不确定为什么支持 List 属性。也许未来的 JavaBeans 规范会支持它们,但后来被撤回了。
关于您的最后一个问题:我认为您必须为每个具有 List 属性的 class 创建一个 BeanInfo class。我希望您可以创建一个通用的 superclass 以使其更容易,例如:
public abstract class ListRecognizingBeanInfo
extends SimpleBeanInfo {
private final BeanDescriptor beanDesc;
private final PropertyDescriptor[] propDesc;
protected ListRecognizingBeanInfo(Class<?> beanClass)
throws IntrospectionException {
beanDesc = new BeanDescriptor(beanClass);
List<PropertyDescriptor> desc = new ArrayList<>();
for (Method method : beanClass.getMethods()) {
int modifiers = method.getModifiers();
Class<?> type = method.getReturnType();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers) &&
!type.equals(Void.TYPE) &&
method.getParameterCount() == 0) {
String name = method.getName();
String remainder;
if (name.startsWith("get")) {
remainder = name.substring(3);
} else if (name.startsWith("is") &&
type.equals(Boolean.TYPE)) {
remainder = name.substring(2);
} else {
continue;
}
if (remainder.isEmpty()) {
continue;
}
String propName = Introspector.decapitalize(remainder);
Method writeMethod = null;
Method possibleWriteMethod =
findMethod(beanClass, "set" + remainder, type);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(Void.TYPE)) {
writeMethod = possibleWriteMethod;
}
Class<?> componentType = null;
if (type.isArray()) {
componentType = type.getComponentType();
} else {
Type genType = method.getGenericReturnType();
if (genType instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) genType;
if (p.getRawType().equals(List.class)) {
Type[] argTypes = p.getActualTypeArguments();
if (argTypes[0] instanceof Class) {
componentType = (Class<?>) argTypes[0];
}
}
}
}
Method indexedReadMethod = null;
Method indexedWriteMethod = null;
if (componentType != null) {
Method possibleReadMethod =
findMethod(beanClass, name, Integer.TYPE);
Class<?> idxType = possibleReadMethod.getReturnType();
if (idxType.equals(componentType)) {
indexedReadMethod = possibleReadMethod;
}
if (writeMethod != null) {
possibleWriteMethod =
findMethod(beanClass, writeMethod.getName(),
Integer.TYPE, componentType);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(
Void.TYPE)) {
indexedWriteMethod = possibleWriteMethod;
}
}
}
if (indexedReadMethod != null) {
desc.add(new IndexedPropertyDescriptor(propName,
method, writeMethod,
indexedReadMethod, indexedWriteMethod));
} else {
desc.add(new PropertyDescriptor(propName,
method, writeMethod));
}
}
}
propDesc = desc.toArray(new PropertyDescriptor[0]);
}
private static Method findMethod(Class<?> cls,
String name,
Class<?>... paramTypes) {
try {
Method method = cls.getMethod(name, paramTypes);
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers)) {
return method;
}
} catch (NoSuchMethodException e) {
}
return null;
}
@Override
public BeanDescriptor getBeanDescriptor() {
return beanDesc;
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return propDesc;
}
}
我遇到了同样的问题。我正在尝试将 StartDate 和 End 日期保存为来自 JSP 的列表,但它没有保存并且值被清除。在我的项目中,有开始日期和结束日期字段。我调试了 BeanUtilsBean
然后我观察到字段没有 writeMethod
。我为我的 class 中的每个字段添加了一个 setter 方法并且它有效。
java.beans.PropertyDescriptor[name=startDateStrings;属性类型=接口java.util.List; readMethod=public java.util.List com.webapp.tradingpartners.TradingPartnerNewForm.getStartDateStrings()]
这是一个 JavaBeans crapspec 问题,它只允许 void setters。如果你想要这个返回 setters,那么你就不能拥有 JavaBeans 兼容性,而且 Lombok 对此无能为力。
理论上,您可以生成两个 setter,但是您必须以不同的方式调用它们,并且每个字段有两个 setter 实在是太糟糕了。
<cc:dateInput property='<%= "startDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
<cc:dateInput property='<%= "endDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
public List<String> getStartDateStrings() {
return startDateStrings;
}
public String getStartDateStrings(int index) {
return startDateStrings.get(index);
}
public void setStartDateStrings(int index, String value) {
startDateStrings.set(index, value);
}
public List<String> getEndDateStrings() {
return endDateStrings;
}
public String getEndDateStrings(int index) {
return endDateStrings.get(index);
}
public void setEndDateStrings(int index, String value) {
endDateStrings.set(index, value);
这个问题看起来很尴尬,但我们在检索 javabean 的 PropertyDescriptors 时遇到了一个奇怪的行为。 以下是一段简单代码在 1.6、1.7 和 1.8 上的执行结果,编译时符合 1.6 标准。
Java 1.6执行:
java.beans.PropertyDescriptor@4ddc1428 <- 不重要 java.beans.IndexedPropertyDescriptor@7174807e <- 是的,我有一个索引 属性
Java 1.7 执行:
java.beans.PropertyDescriptor[姓名=class; 属性类型=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- 不重要 java.beans.IndexedPropertyDescriptor[名称=值; indexedPropertyType=class java.lang.String; indexedReadMethod=public java.lang.String JavaBean.getValues(int)] <- 是的,我有一个索引 属性
Java 1.8执行:
java.beans.PropertyDescriptor[姓名=class; 属性类型=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- 不重要 java.beans.PropertyDescriptor[名称=值; 属性类型=接口java.util.List; readMethod=public java.util.List JavaBean.getValues()] <- 哎哟!这不再是索引 属性!
为什么改变了?
Javabean 规范说明了如何使用索引访问 属性。并没有说必须使用数组作为索引 属性 的容器。我错了吗?
我阅读了规范,第 8.3.3 章讨论了索引属性的设计模式,而不是严格的规则。
如何在不重构所有应用程序的情况下使以前的行为再次出现? < 旧应用程序,需要修改大量代码等...
感谢您的回答,
Java豆子class
import java.util.ArrayList;
import java.util.List;
public class JavaBean {
private List<String> values = new ArrayList<String>();
public String getValues(int index) {
return this.values.get(index);
}
public List<String> getValues() {
return this.values;
}
}
主要class
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class Test {
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor[] descs =
Introspector.getBeanInfo(JavaBean.class).getPropertyDescriptors();
for (PropertyDescriptor pd : descs) {
System.out.println(pd);
}
}
}
来自 JavaBeans 1.01 规范,第 7.2 节“索引属性”:
A component may also expose an indexed property as a single array value.
第 8.3 节描述了 introspection 在没有显式 BeanInfo 的情况下识别的设计模式。第 8.3.3 节说只有数组属性会触发索引属性的自动识别。
你在技术上是正确的;使用数组不是强制性的。但是如果你不这样做,规范说你必须提供你自己的 BeanInfo 来将 属性 公开为索引 属性.
所以您的问题标题的答案是:是的,Java 1.8 符合 JavaBean 规范。
我不确定为什么支持 List 属性。也许未来的 JavaBeans 规范会支持它们,但后来被撤回了。
关于您的最后一个问题:我认为您必须为每个具有 List 属性的 class 创建一个 BeanInfo class。我希望您可以创建一个通用的 superclass 以使其更容易,例如:
public abstract class ListRecognizingBeanInfo
extends SimpleBeanInfo {
private final BeanDescriptor beanDesc;
private final PropertyDescriptor[] propDesc;
protected ListRecognizingBeanInfo(Class<?> beanClass)
throws IntrospectionException {
beanDesc = new BeanDescriptor(beanClass);
List<PropertyDescriptor> desc = new ArrayList<>();
for (Method method : beanClass.getMethods()) {
int modifiers = method.getModifiers();
Class<?> type = method.getReturnType();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers) &&
!type.equals(Void.TYPE) &&
method.getParameterCount() == 0) {
String name = method.getName();
String remainder;
if (name.startsWith("get")) {
remainder = name.substring(3);
} else if (name.startsWith("is") &&
type.equals(Boolean.TYPE)) {
remainder = name.substring(2);
} else {
continue;
}
if (remainder.isEmpty()) {
continue;
}
String propName = Introspector.decapitalize(remainder);
Method writeMethod = null;
Method possibleWriteMethod =
findMethod(beanClass, "set" + remainder, type);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(Void.TYPE)) {
writeMethod = possibleWriteMethod;
}
Class<?> componentType = null;
if (type.isArray()) {
componentType = type.getComponentType();
} else {
Type genType = method.getGenericReturnType();
if (genType instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) genType;
if (p.getRawType().equals(List.class)) {
Type[] argTypes = p.getActualTypeArguments();
if (argTypes[0] instanceof Class) {
componentType = (Class<?>) argTypes[0];
}
}
}
}
Method indexedReadMethod = null;
Method indexedWriteMethod = null;
if (componentType != null) {
Method possibleReadMethod =
findMethod(beanClass, name, Integer.TYPE);
Class<?> idxType = possibleReadMethod.getReturnType();
if (idxType.equals(componentType)) {
indexedReadMethod = possibleReadMethod;
}
if (writeMethod != null) {
possibleWriteMethod =
findMethod(beanClass, writeMethod.getName(),
Integer.TYPE, componentType);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(
Void.TYPE)) {
indexedWriteMethod = possibleWriteMethod;
}
}
}
if (indexedReadMethod != null) {
desc.add(new IndexedPropertyDescriptor(propName,
method, writeMethod,
indexedReadMethod, indexedWriteMethod));
} else {
desc.add(new PropertyDescriptor(propName,
method, writeMethod));
}
}
}
propDesc = desc.toArray(new PropertyDescriptor[0]);
}
private static Method findMethod(Class<?> cls,
String name,
Class<?>... paramTypes) {
try {
Method method = cls.getMethod(name, paramTypes);
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers)) {
return method;
}
} catch (NoSuchMethodException e) {
}
return null;
}
@Override
public BeanDescriptor getBeanDescriptor() {
return beanDesc;
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return propDesc;
}
}
我遇到了同样的问题。我正在尝试将 StartDate 和 End 日期保存为来自 JSP 的列表,但它没有保存并且值被清除。在我的项目中,有开始日期和结束日期字段。我调试了 BeanUtilsBean
然后我观察到字段没有 writeMethod
。我为我的 class 中的每个字段添加了一个 setter 方法并且它有效。
java.beans.PropertyDescriptor[name=startDateStrings;属性类型=接口java.util.List; readMethod=public java.util.List com.webapp.tradingpartners.TradingPartnerNewForm.getStartDateStrings()]
这是一个 JavaBeans crapspec 问题,它只允许 void setters。如果你想要这个返回 setters,那么你就不能拥有 JavaBeans 兼容性,而且 Lombok 对此无能为力。
理论上,您可以生成两个 setter,但是您必须以不同的方式调用它们,并且每个字段有两个 setter 实在是太糟糕了。
<cc:dateInput property='<%= "startDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
<cc:dateInput property='<%= "endDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
public List<String> getStartDateStrings() {
return startDateStrings;
}
public String getStartDateStrings(int index) {
return startDateStrings.get(index);
}
public void setStartDateStrings(int index, String value) {
startDateStrings.set(index, value);
}
public List<String> getEndDateStrings() {
return endDateStrings;
}
public String getEndDateStrings(int index) {
return endDateStrings.get(index);
}
public void setEndDateStrings(int index, String value) {
endDateStrings.set(index, value);