2

Given this class:

public class Node {
  private final List<Node> children;
  public Node(){
    children = new ArrayList<>();
    // possibly pre-populate the list here
  }
  public List<Node> getChildren(){
    return children;
  }
}

What is the best way to create a Node with children in Spring, when some of the children are defined by the class and some should be configurable?

<bean id="node1" class="Node">
  <property name="children">
    <add-item><bean class="Node"/></add-item> <!--pseudocode-->
    <add-item><bean class="Node"/></add-item> <!--pseudocode-->
  </property>
</bean>

The answers I found in Spring IoC docs and forums didn't satisfy me because the proposed solutions involved various degrees of overhead: Additional mapping beans, factory methods, abstract beans to inherit from, ... isn't there something more elegant?

Markus
  • 668
  • 7
  • 27
  • possible duplicate of [How to define a List bean in Spring?](http://stackoverflow.com/questions/2416056/how-to-define-a-list-bean-in-spring) – Timo Jul 01 '15 at 06:55

3 Answers3

3

I would go with injecting the list directly into constructor. This makes it not difficult use a final field:

public class Node {
  private final List<Node> children;
  public Node(List<Node> children){
    this.children = children;
  }
  public List<Node> getChildren(){
    return children;
  }
}

<bean id="node1" class="Node">
  <constructor-arg>
        <list>
            <item><bean class="Node"/></item> <!--pseudocode-->
            ...
        </list>
    </constructor-arg>    
</beans>

EDIT to your comment:

If you want to do any initialization before or after, you can do it simply like this:

...
public Node(List<Node> children){
    this.children = new ArrayList<>();
    // do some initialization before...
    this.children.addAll(children);
    // do some initialization after...
}
...
Thomas Uhrig
  • 30,811
  • 12
  • 60
  • 80
  • yes, good answer, however, what if I had predefined items in the class? (edited my example to clarify) – Markus Jul 01 '15 at 07:05
  • well, I could let the constructor add the provided items after the prepopulated items, that would do – Markus Jul 01 '15 at 07:13
1

I changed Node class a bit

public class Node implements InitializingBean{

private final List<Node> children;

private String name;

public Node() {
    children = new ArrayList<>();
}

@Override
public void afterPropertiesSet() throws Exception {
    //predefined nodes
    Node node = new Node();
    node.setName(":)");
    children.add(node);
}

public String toString(){ return name; }

public List<Node> getChildren() {
    return children;
}

public void setNodes(Node nodes[]){
    if(nodes != null){
        for(Node node: nodes){
            this.children.add(node);
        }
    }
}

public void setChildren(Object something){
    System.out.println(something.getClass());
}
public void setName(String name) { this.name = name; }

}

The trick is to iterate the argument values and add to predefined array list. The reason why I name it setNodes instead of setChildren because Spring will introspect the type of children property from getChildren. If I name the setter as setChildren, I will get IllegalArgumentException.

Of course, I can use ArrayList instead of native array but you said you don't want any overhead. Creating array is nothing, adding objects from array to ArrayList is just creating a reference which is cheap too.

This is the bean definition

<bean id="node1" class="playground.Node">
    <property name="nodes">
        <array >
            <bean class="playground.Node">
                <property name="name" value="A" />
            </bean>
            <bean class="playground.Node">
                <property name="name" value="B" />
            </bean>
        </array>
    </property>
</bean>
hussachai
  • 4,322
  • 2
  • 16
  • 17
  • yes, I also thought of one list property, readable, representing all children and a second list property, writable, to add children. It sure works and is not that bad, but still feels like "adapt domain model to Spring capabilities" instead of "use Spring to adapt to domain model" – Markus Jul 01 '15 at 09:12
  • If you don't want to modify model class at all, you should use Spring java config instead. Another workaround is to use `MethodInvokingFactoryBean` to call `add` method but it's very cumbersome and not elegant at all unless Spring XML supports loop. – hussachai Jul 01 '15 at 17:17
1

You did not specify where children comes from. As a second option you can use @PostContruct method if you don't want to inject List into contstructor:

public class Node {
  private final List<Node> children;
  public Node(){
    children = new ArrayList<>();
  }
  public List<Node> getChildren(){
    return children;
  }

  @PostConstruct
  public void init() {
    // this method will Spring call after dependency injection is done.
    children.add(...)
  }
Gondy
  • 4,925
  • 4
  • 40
  • 46