P Marecki P Marecki - 2 months ago 6
Java Question

How to configure child beans with @Bean annotation

I would like to use Spring's bean inheritance while employing the

@Bean
-type definition of beans. Specifically, let

public class Serwis {
Integer a;
Integer b;
Map<Integer, Integer> m = new HashMap<>();
}


and suppose the xml-based configuration would look like:

<bean id="absSerwis" class="service.Serwis"
p:a="11">
<property name="m">
<map>
<entry key="111" value="111"></entry>
</map>
</property>
</bean>

<bean id="defSerwis" parent="absSerwis"
p:b="12"
/>


which does create the bean
defSerwis
containing a deep copy of the bean
absSerwis
; in particular the contents of
m
are copied. Now, I would like to define beans like
defSerwis
using
@Bean
annotations, like

@Autowired
@Qualifier("absSerwis")
private Serwis absSerwis;

@Bean
public Serwis cccSerwis() {
Serwis s = new Serwis();
BeanUtils.copyProperties(absSerwis, s); //wrong; does shallow copy
return s;
}


what is the proper way to do it?

Answer

For starters what you describe isn't what actually happens. There is no such thing as a deep copy or anything else being made. Lets first investigate what Spring does when using a parent bean. (Also note that it is about bean-definition inheritance NOT class inheritance!);

Given your configuration

<bean id="absSerwis" class="service.Serwis"
      p:a="11">
    <property name="m">
        <map>
            <entry key="111" value="111"></entry>
        </map>
    </property>
</bean>

<bean id="defSerwis" parent="absSerwis"
      p:b="12"
  />

What happens is that for the defSerwis definition it takes the configuration of the parent absSerwis and itself and merges this into a full bean definition. There is thus no such thing as deep copies or copies of beans.

What Spring eventually sees

<bean id="absSerwis" class="service.Serwis"
      p:a="11">
    <property name="m">
        <map>
            <entry key="111" value="111"></entry>
        </map>
    </property>
</bean>

<bean id="defSerwis" class="service.Serwis"
      p:a="11" p:b="12"
      <property name="m">
        <map>
            <entry key="111" value="111"></entry>
        </map>
    </property>
  />

See also this section of the reference guide.

The easiest way is to create a method that constructs your parent, and add from there. This method must not be annotated with @Bean.

@Configuration
public class MyConfiguration {

    private Serwis baseSerwis() {
        Serwis base =  new Serwis();
        base.setA(11);
        Map map = new HashMap();
        map.put(111, 111);
        base.setM(map);
        return base;
    }

    @Bean
    public Serwis absSerwis() {
        return baseSerwis();
    }

    @Bean
    public Serwis defSerwis() {
        Serwis defSerwis = baseSerwis();
        defSerwis.setB(12);
        return defSerwis;
    }
}

This is more or less the equivalent of the xml part.