List of Beans and registering beans programmatically via GenericApplicationContext

73 Views Asked by At

We have some service interfaces such as:

public interface MyAdapterService {

    void doSomething();
}

And consumers, which work on all registered instances of this service:

@Service
@RequiredArgsConstructor
public class MyGenericService {

    private final List<MyAdapterService> adapterServices;

    public void doSomething() {
        adapterServices.forEach(MyAdapterService::doSomething);
    }
}

Depending on how MyAdapterService is registered, they get injected into MyGenericService or not (not always consistantly depending on the MyGenericService variant as well, see below).

Class-level annotations such as @Service work fine:

@Service
@AllArgsConstructor
public class MyClassLevelAnnotatedAdapterService implements MyAdapterService{

    private final MyDependency myDependency;

    @Override
    public void doSomething() {
        // do something
    }
}

Method-level generated beans in configurations seem to work just fine, too:

@Configuration
public class MyConfig {


    @Bean
    public MyOtherXAdapterService myOtherXAdapterService(MyDependency myDependency) {
        return new MyOtherXAdapterService(myDependency);
    }
}

But programmatically registered beans via GenericApplicationContext don't make it always into the injected List of MyAdapterService:

@Configuration
public class MyConfig {

    public MyConfig(MyDependency myDependency, MyConfigurationProperties properties, GenericApplicationContext applicationContext) {
        applicationContext.registerBean("myOtherYAdapterService", MyOtherYAdapterService.class, () -> new MyOtherYAdapterService(myDependency, properties));

        MyOtherYAdapterService myOtherYAdapterService = applicationContext.getBean("myOtherYAdapterService", MyOtherYAdapterService.class);

        applicationContext.registerBean("myWrapperAdapterService", MyWrapperAdapterService.class, () -> new MyWrapperAdapterService(myOtherYAdapterService));
    }

    @Bean
    public MyOtherXAdapterService myOtherXAdapterService(MyDependency myDependency) {
        return new MyOtherXAdapterService(myDependency);
    }

}

Here: the beans get registered in the constructor of a class annotated with @Configuration.

My findings so far:

  • The MyOtherYAdapterService instance can be resolved directly in the line after its registration (getBean())
  • if the configuration does not also define a @Bean-method, the programmatically registered beans don't make it into the list -> Why?
  • if the configuration does also define a @Bean-method, the programmatically registered beans make it into the list sometimes (depending on the consumer) -> Why?

Questions in general:

  • Why does Spring not wait for all @Configuration-classes to be instantiated before resolving lists of beans? The configuration has a dependency on GenericApplicationContext, so it could be aware that some registration might follow. Is there a way to give Spring a hint to wait for the configuration of the GenericApplicationContext?
  • Why does adding a @Bean-method sometimes make a difference? Is this because of some static analyzing?

The referenced implementations/ code snippets:

@AllArgsConstructor
public class MyOtherXAdapterService implements MyAdapterService{

    private final MyDependency myDependency;

    @Override
    public void doSomething() {
        // do something
    }
}

and

@AllArgsConstructor
public class MyOtherYAdapterService implements MyAdapterService{

    private final MyDependency myDependency;
    private final MyConfigurationProperties properties;

    @Override
    public void doSomething() {

    }
}

and

@AllArgsConstructor
public class MyWrapperAdapterService implements MyAdapterService{

    private final MyAdapterService wrappedAdapterService;

    @Override
    public void doSomething() {
        wrappedAdapterService.doSomething();
    }
}

with

@Service
public class MyDependency {

    public void doSomethingElse() {
        // do something else
    }
}

and

@Component
@Getter
@Setter
@ConfigurationProperties("my.config")
public class MyConfigurationProperties {

    private String foo;
    private String bar;

}
1

There are 1 best solutions below

0
Florian H. On

I'm not a Spring expert, but I see following. In your constructor

public MyConfig(MyDependency myDependency, MyConfigurationProperties properties, GenericApplicationContext applicationContext)

there is on one hand the GenericApplicationContext, on the other hand a dependency (MyDependency).

So at the same time some dependency(ies) have to be existing, and some other unknown bean is defined (unknown because only disovered at runtime, so too late). How can Spring know in advance, in which order it should runs the code and instanciates beans? There is no clue to know that a myOtherYAdapterService will be registered.

Give a chance to Spring to know which method defines what, using several small @Bean methods, so Spring sees from the method signature what is being created where. You could also try @Order to influence the order creation, eventually using several @Configuration classes to have it fine-grained, but it can become a headache. You could also try @DependsOn("aBeanName") or perhaps depending on the whole config (not sure if possible), but it's sort of a back-dependency that the dependency injection principle try to avoid.