`
caotan
  • 浏览: 1255 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

JDK1.8中IndexedPropertyDescriptor的改变对BeanUtils的影响

    博客分类:
  • Java
阅读更多

1. BeanUtils的应用

  调用BeanUtils.populate(object, map)可以将一个Map的按照对应的名值对转载到一个Bean对象中。这里有一个高级一点的用法。代码结构为,Father和Child分别继承自Person,Child具有Grade域而Father有Job和Children域,其中Children为一个数组类型的域。

  • Person
import java.util.Date;

public class Person implements java.io.Serializable, Cloneable{
 
    public Person() {
        super();
    }
    private String name;
    private String age;
    private Date birthday;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}


  •  Father
import java.util.ArrayList;
import java.util.List;

public class Father extends Person {
	
	private List<Child> children = new ArrayList<Child>();
	
	private String job;

	public String getJob() {
		return job;
	}
	
	public void setJob(String job) {
		this.job = job;
	}	
	
	public Child getChildren(int index){
		if (this.children.size() <= index){
			this.children.add(new Child());
		}
		return this.children.get(index);
	}
	
	public Person[] getChildren(){
		return (Person[]) children.toArray();
	}
	
	public void setChildren(int index, Child  child) {
        this.children.add(child);
    }

	
}
  •  Child
public class Child extends Person {
	
	private String grade;

	public String getGrade() {
		return grade;
	}

	public void setGrade(String grade) {
		this.grade = grade;
	}
}
  • 类图      

    
  下面的这段代码展示了调用BeanUtils.populate使用一个Map填充一个Father对象。比较特别的,在Map的键值中我们使用了children[0].name这样的字符串来说明需要填充Father的children域,它是一个Child数组。其中中括号里面的0表示数组的索引。

  • BeanUtilTest
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;

public class BeanUtilTest {
	
	public void testPopulate() throws IllegalAccessException, InvocationTargetException
	{
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("name", "tan");
		map.put("birthday", "1980-6-1");
		map.put("children[0].name", "zihui");
		map.put("children[0].birthday", "2008-2-13");
		map.put("children[0].grade", "G3");
		map.put("job", "engineer");		
		
		ConvertUtils.register(new DateLocaleConverter(), Date.class);
		Father f = new Father();
		
		BeanUtils.populate(f, map);
		System.out.println(f.getName());
		System.out.println(f.getJob());
		System.out.println(f.getChildren(0).getName());
		System.out.println(f.getChildren(0).getGrade());
	}
	
	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		
		BeanUtilTest but = new BeanUtilTest();
		but.testPopulate();
	}
}
  •  执行结果

  此代码在JDK1.7.0_60的环境中执行结果如下:

tan
engineer
zihui
G3

 

2. 升级JDK1.8.0_102之后

把jre library升级成JDK1.8.0_102执行此代码出错。错误信息如下:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2116)
	at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:542)
	at org.apache.commons.beanutils.PropertyUtilsBean.getIndexedProperty(PropertyUtilsBean.java:446)
	at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:806)
	at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:884)
	at org.apache.commons.beanutils.BeanUtilsBean.setProperty(BeanUtilsBean.java:894)
	at org.apache.commons.beanutils.BeanUtilsBean.populate(BeanUtilsBean.java:821)
	at org.apache.commons.beanutils.BeanUtils.populate(BeanUtils.java:431)
	at BeanUtilTest.testPopulate(BeanUtilTest.java:27)
	at BeanUtilTest.main(BeanUtilTest.java:37)
Caused by: java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LPerson;
	at Father.getChildren(Father.java:27)
	... 14 more

 

3. 寻找错误原因

通过调试jdk 1.7和jdk 1.8,发现直接原因是jdk1.7下PropertyUtilsBean.getIndexedProperty(Object bean,String name, int index)在521行返回,而jdk1.8在542行抛出异常。

  • 代码片段17行为源代码521行,38行为源代码542行
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        // Call the indexed getter method if there is one
        if (descriptor instanceof IndexedPropertyDescriptor) {
            Method readMethod = ((IndexedPropertyDescriptor) descriptor).
                    getIndexedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
            if (readMethod != null) {
                Object[] subscript = new Object[1];
                subscript[0] = new Integer(index);
                try {
                    return (invokeMethod(readMethod,bean, subscript));
                } catch (InvocationTargetException e) {
                    if (e.getTargetException() instanceof
                            IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException)
                                e.getTargetException();
                    } else {
                        throw e;
                    }
                }
            }
        }

        // Otherwise, the underlying property must be an array
        Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name + "' has no " +
                    "getter method on bean class '" + bean.getClass() + "'");
        }

        // Call the property getter and return the value
        Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);

   进一步阅读代码,我们可以判断出jdk1.7和jdk1.8对person的children property返回的PropertyDescriptor不同,导致了这段代码出现了异常。jdk1.7返回的是IndexedPropertyDescriptor,而jdk1.8返回的则不是IndexedPropertyDescriptor。

 

4. 验证错误原因

  简化测试代码如下

  • PropertyDescriptorTest
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;

public class PropertyDescriptorTest {

	public static void main(String[] args) throws IntrospectionException {
		BeanInfo info2 = Introspector.getBeanInfo(Father.class);
        PropertyDescriptor[] descriptors2 = info2.getPropertyDescriptors();
        for (int i = 0; i < descriptors2.length; i++) {
            System.out.println(descriptors2[i].getClass().getName() + ":" + descriptors2[i].getName());
        }

	}

}

 

  • jdk1.7的测试结果

 

java.beans.PropertyDescriptor:age
java.beans.PropertyDescriptor:birthday
java.beans.IndexedPropertyDescriptor:children
java.beans.PropertyDescriptor:class
java.beans.PropertyDescriptor:job
java.beans.PropertyDescriptor:name

 

  • jdk1.8的测试结果

 

java.beans.PropertyDescriptor:age
java.beans.PropertyDescriptor:birthday
java.beans.PropertyDescriptor:children
java.beans.PropertyDescriptor:class
java.beans.PropertyDescriptor:job
java.beans.PropertyDescriptor:name

   以上测试结果证明了我们的猜测。

 

5.比较jdk1.7和jdk1.8源代码,找出根本原因

  java.beans.Introspector类通过getBeanInfo产生了一个BeanInfo来描叙一个java bean,BeanInfo中包含每个域的描叙PropertyDescriptor,由getPropertyDescriptors返回一个PropertyDescriptor数组。

  而在初始化BeanInfo的方法Introspector.getBeanInfo(Father.class)中,通过调试,可以看出 PropertyDescriptor是在Introspector的私有方法processPropertyDescriptors中被初始化的。比较jdk1.7和jdk1.8的源代码,可以看出这个私有方法有很大的变动。

  进一步调试,我发现影响children的PropertyDescriptor类型被判断成PropertyDescriptor的关键代码是jdk1.8 类Introspector的748到764行的逻辑。代码如下:

                if (pd == null) {
                    pd = ipd;
                } else {
                    Class<?> propType = pd.getPropertyType();
                    Class<?> ipropType = ipd.getIndexedPropertyType();
                    if (propType.isArray() && propType.getComponentType() == ipropType) {
                        pd = pd.getClass0().isAssignableFrom(ipd.getClass0())
                                ? new IndexedPropertyDescriptor(pd, ipd)
                                : new IndexedPropertyDescriptor(ipd, pd);
                    } else if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {
                        pd = pd.getClass0().isAssignableFrom(ipd.getClass0())
                                ? new PropertyDescriptor(pd, ipd)
                                : new PropertyDescriptor(ipd, pd);
                    } else {
                        pd = ipd;
                    }
                }

  反观jdk1.7的代码,我们可以看出此逻辑为jdk1.8独有的逻辑,初步判读jdk1.8针对IndexedPropertyDescriptor的判断有了一些新的特征。通过调试,发现因为没有满足以下条件,所以children属性被判断成普通的PropertyDescriptor而非我们期望的IndexedPropertyDescriptor。

 

if (propType.isArray() && propType.getComponentType() == ipropType) { 

   其中propType.isArray()返回为真,因此我们判断出getChildren方法的返回类型必须一致才能够满足条件。

 

6.修改

修改Father类的定义。

  • new Father代码如下: 
import java.util.ArrayList;
import java.util.List;

public class Father extends Person {
	
	private List<Child> children = new ArrayList<Child>();
	
	private String job;

	public String getJob() {
		return job;
	}
	
	public void setJob(String job) {
		this.job = job;
	}
	
	
	public Child getChildren(int index){
		if (this.children.size() <= index){
			this.children.add(new Child());
		}
		return this.children.get(index);
	}
	
	//Fix return type, keep it consistance with getChildren(int index)
	public Child[] getChildren(){
		return (Child[]) children.toArray();
	}
	
	public void setChildren(int index, Child  child) {
        this.children.add(child);
    }	
}

   在jkd1.8上执行测试方法,结果符合我们的期望:

 

java.beans.PropertyDescriptor:age
java.beans.PropertyDescriptor:birthday
java.beans.IndexedPropertyDescriptor:children
java.beans.PropertyDescriptor:class
java.beans.PropertyDescriptor:job
java.beans.PropertyDescriptor:name
  验证主程序,主程序成功执行,没有异常抛出: 
tan
engineer
zihui
G3
  

7.思考

  java.beans在jdk1.8中针对PropertyDescriptor的一些调整导致common-beanutils出现该错误。而common-beanutils在现如今的项目中使用非常普遍,所以当建议项目在从jdk1.7升级到jdk1.8的过程中,要有针对性的组织和该代码相关的测试案例,从而避免交付结果中存在潜在的问题。

 

8.相关资料

BeanUtils: http://commons.apache.org/proper/commons-beanutils/

BeanUtils: commons-beanutils-1.9.2

JDK1.7: JDK1.7.0_60

JDK1.8: JDK1.8.0_102

  • 大小: 2.8 KB
分享到:
评论

相关推荐

    mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系

    mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk1.8安装包!mac系统jdk...

    JDK1.8中文文档 JDK1.8中文 jkd8中文文档 JDK中文版

    再者,JDK1.8引入了默认方法(Default Methods)到接口中,这是一个重大的设计改变。默认方法允许在接口中定义具有实现的方法,这样可以在不破坏已有实现的情况下为接口添加新的功能。这在升级API时尤其有用,避免了...

    jdk1.8 JDK1.8 中文 CHM

    8. **Map接口的改进**:`Map`接口增加了几个新的方法,如`putIfAbsent()`, `remove()`, `replace()`, 和`compute()`, `merge()`,使得在并发环境中对Map的操作更加安全和便捷。 9. **类型推断的增强**:编译器现在...

    JDK 1.8中文API文档

    JDK 1.8对日期和时间API进行了重大改进,引入了`java.time`包,包含`LocalDate`、`LocalTime`、`LocalDateTime`等类,替代了旧的`java.util.Date`和`java.util.Calendar`,提供了更强大、更易用的时间日期处理功能...

    jdk 1.8 中文api文档

    **JDK 1.8 中文API文档**是Java开发者的重要参考资料,它包含了JDK 1.8版本的所有核心类库、接口、方法和异常的详细说明,方便开发者理解和使用。这个文档是基于谷歌翻译的版本,虽然可能存在部分翻译不准确的情况,...

    jdk1.8 api 中文文档

    **标题解析:**"JDK1.8 API 中文文档" 这个标题指的是Java Development Kit (JDK) 1.8版本的API(Application Programming Interface)的中文解释文档。API是一系列预先定义的函数、类、接口和枚举,用于帮助程序员...

    JDK1.8手册,中文

    在接口中,JDK 1.8引入了默认方法,允许在接口中定义带有实现的方法。这使得接口可以在不破坏向后兼容性的情况下添加新的方法。例如,`public interface MyInterface { default void myMethod() { // 实现代码 } }` ...

    jdk1.8中文文档.rar

    结合JDK 1.8的API中文文档,开发者可以学习如何在实际项目中应用上述特性,例如使用Lambda简化集合操作,利用Stream API进行复杂的数据处理,或者通过新的日期和时间API来处理日期相关的问题。此外,文档还详细解释...

    JDK1.8 官网版本jdk1.8

    8. **并行GC的优化**:在垃圾收集方面,JDK1.8对G1(Garbage First)垃圾收集器进行了优化,使其更适合大规模服务端应用,提高了应用程序的响应速度和吞吐量。 9. ** Nashorn JavaScript引擎**:JDK1.8包含了...

    JDK1.8压缩包下载解压即用

    提供两种资源方式:(JDK1.8压缩包64位Windows版本)上面JDK1.8压缩包直接下载(解压一下就可以用),想自己下载的下方官网网址自行查找 官网下载地址:https://www.oracle.com/java/technologies/downloads/ JDK...

    JDK1.8 中文API手册

    《JDK1.8中文API手册》是Java开发者的重要参考资料,它详尽地列出了Java Development Kit 1.8版本中的各种类、接口、方法和常量,为开发者提供了全面的编程指南。以下是对其中一些关键知识点的详细介绍: 1. **...

    jdk1.8中文.CHM

    jdk1.8中文.CHM

    JDK1.8_中文有道翻译版.zip

    JDK1.8是Java 8版本的开发工具包,它的发布对Java社区产生了深远影响,引入了许多新的特性和改进。 1. **模块化系统(Project Jigsaw)** JDK1.8首次引入了模块化系统,这是一个重大变革,旨在提高程序的可维护性...

    JDK1.8 中文API文档 高清完整CHM版

    JDK1.8 API 中文 java帮助文档 JDK API java 帮助文档 百度翻译 JDK1.8 API 中文 java帮助文档 Java最新帮助文档 本帮助文非人工翻译。准确性不能保证,请与英文版配合使用

    dubbo admin jdk1.8

    在JDK1.8环境下部署Dubbo Admin是为了确保与 Dubbo 兼容性,因为不同的Dubbo版本可能对JDK有特定的要求。 【描述】中的“dubbo admin jdk1.8的环境的tomcat解压文件,亲测可以用”意味着这个压缩包包含了已经在JDK...

    jdk1.8中文文档.zip

    这份"jdk1.8中文文档.zip"包含了完整的JDK 1.8中文版官方文档,对于正在学习或工作中使用Java 1.8的开发者来说,是一份非常宝贵的学习资源。 首先,文档中的"Java SE 8 API"部分详尽地介绍了JDK 1.8提供的所有类库...

    JDK1.8版本免安装解压缩版

    1. **IDE集成**:大部分现代Java集成开发环境(如Eclipse、IntelliJ IDEA)都支持JDK 1.8,只需在IDE中配置好JDK路径即可开始开发。 2. **编写Java代码**:利用新特性如Lambda表达式和Stream API,可以编写出更加...

    jdk1.8压缩包下载

    JDK 1.8是Java历史上的一个重要版本,它引入了许多新特性,增强了性能,并且对开发者提供了更多的便利。下面将详细讨论JDK 1.8中的关键知识点。 1. **Lambda表达式**:这是JDK 1.8最重要的更新之一,它简化了处理...

    jdk 1.8 Mac dmg

    JDK 1.8作为Java生态系统中的一个重要里程碑,不仅引入了诸如Lambda表达式、Stream API等重大创新,还改进了原有的API和工具集,显著提升了开发效率和程序性能。对于Mac OS X用户来说,通过简单的步骤即可安装配置好...

    JDK1.8 windows zip解压缩版

    使用JDK1.8 Windows Zip解压缩版,用户只需将压缩包解压到指定目录,设置好环境变量(如JAVA_HOME、PATH),即可在命令行中使用javac进行编译,java命令运行程序。对于开发人员来说,这是一个快速启动Java开发的便捷...

Global site tag (gtag.js) - Google Analytics