简体   繁体   中英

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?

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.

If you would like to make a deep copy of object then you could:

1) program it yourself to do so.

2) use java serialization to do so (serializing and deserializing could create a deep copy), but this requireas that all objects in your data structure implements the serializable interface. There are also other liberaries that could be used for that (xStream for instance).

3) Spring by default creates only one instance of a particular bean (aka Singleton), you could annotate it with @Prototype to let Spring know that you would like to get a new object each time you need it.

By using a builder + copy method

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

    private Serwis(Builder builder) {
        a = builder.a;
        b = builder.b;
        m = builder.m;
    }

    public static Builder builder() {
        return new Builder();
    }

    public Builder copy(Serwis copy) {
        return builder()
            .a(a)
            .b(b)
            .m(m);
    }

    public static final class Builder {
        private Integer a;
        private Integer b;
        private Map<Integer, Integer> m = new HashMap<>();

        private Builder() {
        }

        public Builder a(Integer val) {
            a = val;
            return this;
        }

        public Builder b(Integer val) {
            b = val;
            return this;
        }

        public Builder m(Map<Integer, Integer> val) {
            m.putAll(val);
            return this;
        }

        public Serwis build() {
            return new Serwis(this);
        }
    }
}

And the configuration class like this:

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

@Bean
public Serwis cccSerwis() {
    return absSerwis.copy().build();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM