I hava Foo and Item class as below.
import java.util.ArrayList;
import java.util.List;
public class Foo {
private Long id;
private List<Item> items;
public Foo(Long id) {
this.id = id;
this.items = new ArrayList<Item>();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
}
public class Item {
private String bar;
public Item(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
@Override
public String toString() {
return "Item{" + "bar='" + bar + '\'' + '}';
}
}
When I am copying the Foo class using spring BeanUtils the reference for the list field is not changing.
import org.springframework.beans.BeanUtils;
public class SimpleCopyMain {
public static void main(String[] args) {
Foo foo = new Foo(1L);
foo.getItems().add(new Item("item1"));
foo.getItems().add(new Item("item2"));
Foo fooSnapShot = new Foo(100L);
BeanUtils.copyProperties(foo,fooSnapShot);
foo.setId(999L);
System.out.println("fooSnapShot id field value is not changing as expected : " + fooSnapShot.getId());
foo.getItems().add(new Item("item3"));
System.out.println("fooSnapShot items value is changing unexpectedly : " + fooSnapShot.getItems());
}
}
The output for SimpleCopyMain class is below :
fooSnapShot id field value is not changing as expected : 1
fooSnapShot items value is changing unexpectedly : [Item{bar='item1'}, Item{bar='item2'}, Item{bar='item3'}]
However when I create a new instance for the list field and copy the references one by one, I get the behaviour as I expected.
import java.util.ArrayList;
import org.springframework.beans.BeanUtils;
public class CopyMain {
public static void main(String[] args) {
Foo foo = new Foo(1L);
foo.getItems().add(new Item("item1"));
foo.getItems().add(new Item("item2"));
Foo fooSnapShot = new Foo(100L);
BeanUtils.copyProperties(foo, fooSnapShot);
fooSnapShot.setItems(new ArrayList<Item>(foo.getItems().size()));
for(int i = 0; i < foo.getItems().size(); i++){
Item anItem = new Item("");
BeanUtils.copyProperties(foo.getItems().get(i), anItem);
fooSnapShot.getItems().add(anItem);
}
foo.setId(999L);
System.out.println("fooSnapShot id field value is not changing as expected : " + fooSnapShot.getId());
foo.getItems().add(new Item("item3"));
System.out.println("fooSnapShot items value is is not changing : " + fooSnapShot.getItems());
}
}
Here is the output :
fooSnapShot id field value is not changing as expected : 1
fooSnapShot items value is is not changing : [Item{bar='item1'}, Item{bar='item2'}]
And my pom :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.question</groupId>
<artifactId>beanutils</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
</dependencies>
</project>
Why is the spring beanutils not clone a list field ?
According to BeanUtil copyProperities method implementation, Spring is copying your data via Getters and Setters. If you have primitives like Integer it's ok, but for your List field, you are passing reference in Setter.
If you'd like it to work, you need to change your setter to:
public void setItems(List<Item> items) {
this.items = new ArrayList<>(items);
}
This will do also shallow copy, but you won't have list reference.
If you look at the spring's BeanUtils.copyProperties you can see that all is doing is performing a shallow copy of the properties meaning only properties with primitive values will be cloned, all other properties will copied by reference. Behind the scenes Spring is using PropertyDescriptor and calling the getter on the source property and calling the setter in the target property.
So when you call BeanUtils.copyProperties(foo, fooSnapShot);
at that moment foo and fooSnapShot share the same reference to the items list, and that's why the list can be changed through the foo or fooSnapshot instances, however, in your second case you are giving fooSnapShot a reference to a different List fooSnapShot.setItems(new ArrayList<Item>(foo.getItems().size()));
and that's why you get your expected results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With