simonC
simonC

Reputation: 4327

How to properly overload Spring bean configuration

I'm having two spring(4.2) java configurations, one in a base module and one in a client specific module:

@Configuration
public class BaseConfig {

    @Bean
    public A getA() {
        return new A("aaa");
    }

}

@Configuration
public class ClientConfig {

    @Bean
    public A getA() {
        return new A("bbbb");
    }

}

During the app load there is always BaseConfig.getA() called, how can I ovverride the base bean factory configuration to have some client specific stuff?

Upvotes: 5

Views: 3669

Answers (6)

Klaus Groenbaek
Klaus Groenbaek

Reputation: 5035

This is an answer to a comment above, but since comments have limited formating and size, I will reply with an answer instead.

how does spring define the ordering and overriding of beans when loading configuration files

It depends what you mean by loading multiple configuration. If you have one spring context and have two classes with @Configuration and do a component scan, then Spring will build the dependency tree, and which ever context (bean) is loaded last will define the bean (as it overrides the fist definition).

If you have have multiple Spring contexts in a parent child relation, then the child and see parent beans, and will also 'override' parent beans if you use child.getBean(type.class). The parent can't see bean from children.

Using @Primary. If you have a Spring context (can come from multiple configurations) that defines two beans of the same type, you will not be able to use context.getBean(type.class) or @AutoWired (without @Qualifier) because you have multiple beans of the same type. This behaviour can be altered if one of the beans is @Primary. I try to avoid the use of @Primary in my own code, but I can see it is used heavily used in Spring boots auto configure system, so I think it has some subtle usage when it comes to framework design.

Here is a small example, note that if you load configuration classes directly, they don't need to have the @Configuration annotation.

public class ParentChildContext {

    public static void main(String[] args) {
        parentChildContext();
        twoConfigurationsSameContext();

    }

    private static void twoConfigurationsSameContext() {

        ApplicationContext ctx = new AnnotationConfigApplicationContext(Parent.class, Child.class);
        // if you have two beans of the same type in a context they can be loaded by name
        Object childA = ctx.getBean("childA");
        System.out.println("childA = " + childA);

        Object parentA = ctx.getBean("parentA");
        System.out.println("parentA = " + parentA);
        // since both configurations define A, you can't do this
        ctx.getBean(A.class);
    }

    private static void parentChildContext() {
        ApplicationContext parentCtx = new AnnotationConfigApplicationContext(Parent.class);

        A parentA = parentCtx.getBean(A.class);
        System.out.println("parent = " + parentA);

        AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
        childCtx.register(Child.class);
        childCtx.setParent(parentCtx);
        childCtx.refresh();

        A childA = childCtx.getBean(A.class);
        System.out.println("child = " + childA);
    }


    public static class Parent {
        @Bean
        //@Primary // if you enable @Primary parent bean will override child unless the context is hierarchical
        public A parentA() {
            return new A("parent");
        }
    }

    public static class Child {
        @Bean
        public A childA() {
            return new A("child");
        }
    }


    public static class A {
        private final String s;

        public A(String s) {
            this.s = s;
        }
        @Override
        public String toString() {
            return "A{s='" + s + "'}";
        }
    }
}

Upvotes: 0

KayV
KayV

Reputation: 13835

@Profile Annotation can be used for this...

@Configuration
@Profile("base")
public class BaseConfig {

    @Bean
    public A getA() {
        return new A("aaa");
    }

}

@Configuration
@Profile("client")
public class ClientConfig {

    @Bean
    public A getA() {
        return new A("bbbb");
    }

}

Use the following link https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/

Upvotes: 0

Klaus Groenbaek
Klaus Groenbaek

Reputation: 5035

Personally I would NEVER override a bean in spring! I have seen people spend too much time debugging issues related to this. For the same reason I would never use @Primary.

In this case I would have 3 contexts

  1. Context that contains beans unique to the parent context
  2. Context that contains beans unique to the child context
  3. Abstract context that contains all shared beans.

This way you will specify the 2 contexts to load. This could be done programatically, or using profiles. Potentially you will need more contexts, because you probably want some of your beans to be different in tests.

Upvotes: 2

pezetem
pezetem

Reputation: 2541

I think that you should take a look at the @Profile annotation. You could simply split configuration into different base and client specific one like:

@Configuration
@Profile("base")
public class BaseConfig {

    @Bean
    public A getA() {
        return new A("aaa");
    }

}

@Configuration
@Profile("client")
public class ClientConfig {

    @Bean
    public A getA() {
        return new A("bbbb");
    }

}

now run the specific profile by adding

  • @ActiveProfiles("base") on application main method
  • spring.profiles.active=base entry in application.properties
  • or even pass profile name into jvm params

Upvotes: 1

g-t
g-t

Reputation: 1533

If you include both configurations you can check Primary annotation: Primary

Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.

Upvotes: 0

Harshil Sharma
Harshil Sharma

Reputation: 2035

I'm not sure on how to extend bean config classes. One solution is to mark the bean in ClientConfig with @Primary annotation. This will cause the ClientConfig definition of bean A to be used.

Upvotes: 0

Related Questions