基于 apache BeanUtils 实现 JavaBean克隆及属性拷贝

参考博客:

基于Spring BeanUtils 实现 JavaBean克隆及属性拷贝

基于apache BeanUtils 实现 JavaBean克隆及属性拷贝

基于 MapStruct实现 JavaBean克隆及属性拷贝

简介

BeanUtils提供了对 Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。
在使用BeanUtils时,需要注意:所操作的JavaBean必须是public的,不然BeanUtils会抛异常。
BeanUtils主要提供了对于JavaBean的各种操作,它提供了如下4个重要的包:

  • org.apache.commons.beanutils
  • org.apache.commons.beanutils.converters
  • org.apache.commons.beanutils.locale
  • org.apache.commons.beanutils.locale.converters
    其实除了第一项之外,其它的都是后来版本才加上去的。其中:
  • converters 包专门用来处理不同object之间如何转换的问题。
  • locale包用来处理国际化的问题。
    其中最常用到的类是 PropertyUtils 及 ConvertUtils 还有 DynaBeans。
    所有的XXXUtils类都提供的是静态方法,可以直接调用,其主要实现都在相应的XXXUtilsBean中,当然在使用时我们也可以直接调用那些XXXUtilsBean,功能都一样:
  • BeanUtils:BeanUtilsBean
  • ConvertUtils:ConvertUtilsBean
  • PropertyUtils:PropertyUtilsBean

注意由于本技术是用反射技术实现的,所以在实际项目中不建议使用

BeanUtils

Maven依赖

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

简介

BeanUtils是BeanUtils包里比较常用的一个工具类,在使用的时候需要导入相应的jar包,它主要用于JavaBean克隆及属性拷贝。

BeanUtils将属性分成如下表所示的3种类型:

名称 举例
Simple 简单类型 Stirng、int……
Indexed 索引类型 数组、arrayList……
Maped Map HashMap……

访问不同类型的数据可以直接调用BeanUtils的getProperty()和setProperty()方法:

  • public static String getProperty(Object bean, String name)
    获取指定名称的属性的值,BeanUtils会使用ConvertUtils类把字符串转为Bean属性的真正类型。该方法支持直接访问内嵌对象的属性,只要使用点号分隔。
  • public static void setProperty(Object bean, String name, Object value)
    为指定bean的指定属性赋值。
    这两个方法都只有2个参数,第一个是JavaBean对象,第二个是要操作的属性名。
    举例:
  • 对于Simple类型,参数二直接写属性名即可:
System.out.println(BeanUtils.getProperty(user, "name"));
System.out.println(BeanUtils.getProperty(order, "address.city"));
  • 对于Indexed,则为”属性名[索引值]”,注意,这里对于ArrayList和数组都可以用同样的方式进行操作。
BeanUtils.setProperty(user, "addr[1]", "BeiJing");
System.out.println(BeanUtils.getProperty(user, "addr[1]"));
  • 对于Map类型,则需要以”属性名(key值)”的形式:
System.out.println(BeanUtils.getProperty(user, "address (addr2)"));
HashMap<String,String> map = new HashMap<String,String>();
map.put("tel1","13523541486");
map.put("tel2","17098150596");
BeanUtils.setProperty(user,"tels",map);
System.out.println(BeanUtils.getProperty(user, "tels (tel2)"));
  • 当然这3种方式也可以组合使用!
System.out.println(BeanUtils.getProperty(obj, "employee[1].name"));
  • BeanUtils还支持List和Map类型的属性。比如:
BeanUtils.getProperty(order, "customers[1].name");//取得顾客列表中第一个顾客的名字
  • 与getProperty类似的还有getIndexedProperty, getMappedProperty方法
    -Collection: 提供index
BeanUtils.getIndexedProperty(order,"items",1);
//或者
BeanUtils.getIndexedProperty(order,"items[1]");
  • Map: 提供Key Value //key-value goods_no=111
BeanUtils.getMappedProperty(order, "items","111"); 
或者
BeanUtils.getMappedProperty(order, "items(111)");

示例

创建实体类User和Order,两者之间的关系是User:Order = 1:N,即一个用户对应多个Order,具体代码如下:

public class Address {
	private int id;
	private String email;
    private List<String> addrs; 
    //…… 不包含addrs的构造方法
}	
public class Emp {
	private int id;
	private String name;
	private Date birth;
	private Address address;
	private String[] array;
	private List<String> list;
    private Map<String, Integer> maps;
    //……不包含array、list、maps的构造方法
}
public class Dept {
	private int deptno;
	private String dname;
	private String loc;
}	

备注:上面实体类,均省略了getter/setter方法、toString方法、默认构造方法、全参的构造方法。

  • 演示如何利用BeanUtils为对象的指定属性赋值:
@Test
public void testSetProperty() throws Exception {
	Address address = new Address();
	BeanUtils.setProperty(address, "id", 20);
	BeanUtils.setProperty(address, "email", "hcitlife@hotmail.com");
	List<String> addrs = Arrays.asList(new String[] { "BEIJING", "SHANGHAI" });
	BeanUtils.setProperty(address, "addrs", addrs);
	System.out.println(address);
}
  • 演示如何利用BeanUtils获取对象属性的值:
    在测试类中提供如下代码:
private void init(Address address, Emp emp) throws Exception {
	List<String> addrs = Arrays.asList(new String[] { "BEIJING", "SHANGHAI" });
	address.setAddrs(addrs);
	emp.setArray(new String[] { "aaaa", "bbbb", "ccc" });
	Map<String, Integer> maps = new HashMap<String, Integer>();
	maps.put("a", 1111);
	maps.put("b", 2222);
	maps.put("c", 3333);
	emp.setMaps(maps);
	emp.setList(Arrays.asList(new String[] { "111", "222", "333" }));
}
@Test
public void testGetProperty() throws Exception {
	Address address = new Address(20, "hcitlife@hotmail.com");
	Emp emp = new Emp(7788, "ZHANGSAN", new Date(), address);
	init(address, emp);
	// 输出对象的某个属性的值
	System.out.print(BeanUtils.getProperty(address, "id") + "\t");
	System.out.println(BeanUtils.getProperty(emp, "birth"));
	// 输出对象的内嵌属性的值
	System.out.println(BeanUtils.getProperty(emp, "address.email"));
	// 输出嵌套字段的值
	System.out.println(BeanUtils.getNestedProperty(emp, "address.email"));
	// 访问Map中元素的值
	System.out.print(BeanUtils.getProperty(emp, "maps") + "\t");
	System.out.print(BeanUtils.getProperty(emp, "maps.a") + "\t");
	System.out.print(BeanUtils.getProperty(emp, "maps(b)") + "\t");
	System.out.println(BeanUtils.getMappedProperty(emp, "maps", "c"));
	// 访问数组字段的值
	System.out.print(BeanUtils.getProperty(emp, "array[0]") + "\t");
	String[] array = BeanUtils.getArrayProperty(emp, "array");
	System.out.print(array[1] + "\t");
	System.out.println(BeanUtils.getIndexedProperty(emp, "array", 2));
	// 访问集合字段的值
	System.out.print(BeanUtils.getProperty(emp, "list") + "\t");
	System.out.print(BeanUtils.getArrayProperty(emp, "list")[1] + "\t");
	System.out.println(BeanUtils.getIndexedProperty(emp, "list", 2));
	// 综合
	System.out.println(BeanUtils.getProperty(emp, "address.addrs[1]"));
}
  • public static void BeanUtils.copyProperties(Object dest,Object orig)
    通过反射将orig对象的值拷贝给dest对象(前提是对象中属性的名字相同),这种拷贝是浅拷贝,复制后的2个Bean的同一个属性可能拥有同一个对象,在使用时要小心,特别是对于属性为自定义类的情况。
@Test
public void testCopyProperties() throws Exception {// 拷贝属性
	Address address = new Address(20, "hcitlife@hotmail.com");
	address.setAddrs(Arrays.asList(new String[] { "BEIJING", "SHANGHAI" }));
	Emp emp = new Emp();
	emp.setBirth(new Date());//Date类型的属性必须指定
	emp.setAddress(address);
	Emp emp2 = new Emp();
	BeanUtils.copyProperties(emp2, emp);
	System.out.println(emp2.getAddress());
	System.out.println(emp.getAddress() == emp2.getAddress()); //------①
	emp.getAddress().setEmail("lianghecai52171314@126.com");
	System.out.println(emp2.getAddress());
}

编号①处输出结果为true,表示两个Emp的address指赂了同一个Address对象,所以在下面代码中即使只是改变了emp的address的值,emp2的值也随之发生变化了。

  • public static void BeanUtils.copyProperty(Object bean, String name, Object value)
    将value值拷贝给bean的name属性,这种拷贝是浅拷贝。BeanUtils在对Bean赋值是时会进行类型转化,即发现两个JavaBean的同名属性为不同类型时,BeanUtils的copyProperty()方法会在支持的数据类型范围内进行转换。

BeanUtils默认支持的转换类型如下:

数据类型 数据类型
java.lang.BigDecimal java.lang.BigInteger
boolean 、 java.lang.Boolean byte 、 java.lang.Byte
Char 、java.lang.Character java.lang.Class
double 、 java.lang.Double float 、 java.lang.Float
int 、 java.lang.Integer long 、 java.lang.Long
short 、 java.lang.Short java.lang.String
java.sql.Date java.sql.Time
java.sql.Timestamp

需要注意的是,java.util.Date是不被支持的,而它的子类java.sql.Date是被支持的。因此如果对象包含时间类型的属性,且希望被转换的时候,一定要使用java.sql.Date类型。否则在转换时会提示argument mistype异常。

@Test
public void testCopyProperty() throws Exception {// 拷贝一个值给目标Bean的一个属性
	Address address = new Address(20, "hcitlife@hotmail.com");
	List<String> addrs = Arrays.asList(new String[] { "BEIJING", "SHANGHAI" });
	address.setAddrs(addrs);
	Emp emp = new Emp();
	emp.setBirth(new Date());//Date类型的属性必须指定
	emp.setAddress(address);
	Emp emp2 = new Emp();
	BeanUtils.copyProperty(emp2, "address", address);
	System.out.println(emp.getAddress() == emp2.getAddress());
	address.setAddrs(Arrays.asList(new String[] { "beijing", "shanghai" }));
	System.out.println(emp2.getAddress());
}
  • public static Object BeanUtils.cloneBean(Object bean)
    拷贝Bean,这种拷贝是浅拷贝。
@Test
public void testCloneBean() throws Exception { // 拷贝对象
	Address address = new Address(20, "hcitlife@hotmail.com");
	List<String> addrs = Arrays.asList(new String[] { "BEIJING", "SHANGHAI" });
	address.setAddrs(addrs);
	Emp emp = new Emp();
	emp.setBirth(new Date());//Date类型的属性必须指定
	emp.setAddress(address);
	Emp emp2 = (Emp) BeanUtils.cloneBean(emp);
	System.out.println(emp2.getAddress());
	System.out.println(emp.getAddress() == emp2.getAddress());
	emp.getAddress().setEmail("lianghecai52171314@126.com");
	System.out.println(emp2.getAddress());
}
  • static java.util.Map describe(java.lang.Object bean)
    返回一个Object中所有的可读属性,并将属性名/属性值放入一个Map中,另外还有一个名为class的属性,属性值是Object的类名,事实上class是java.lang.Object的一个属性。
@Test
public void testDescribe() throws Exception {
	Address address = new Address(20, "hcitlife@hotmail.com");
	List<String> addrs = Arrays.asList(new String[] { "BEIJING", "SHANGHAI" });
	address.setAddrs(addrs);
	Map<String, Object>  map = BeanUtils.describe(address);
	map.forEach((k, v) -> System.out.println(k + " : " + v));
}
  • static void populate(java.lang.Object bean, java.util.Map properties)
    用于将Map装配成一个对象。
@Test
public void testPopulate() throws Exception {
	Map<String, Object> map = new HashMap<>();
	map.put("id", 20);
	map.put("email", "hcitlife@hotmail.com");
	map.put("addrs", Arrays.asList(new String[] { "BEIJING", "SHANGHAI" }));
	Address address2 = new Address();
	BeanUtils.populate(address2, map);
	System.out.println(address2);
}