Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring BeanUtils copy properties with a field of List

Tags:

java

spring

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 ?

like image 615
cgon Avatar asked Feb 26 '17 14:02

cgon


2 Answers

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.

like image 76
hya Avatar answered Oct 27 '22 01:10

hya


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.

like image 40
artemisian Avatar answered Oct 27 '22 01:10

artemisian