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
MyOtherYAdapterServiceinstance 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 onGenericApplicationContext, 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 theGenericApplicationContext? - 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;
}
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
myOtherYAdapterServicewill be registered.Give a chance to Spring to know which method defines what, using several small
@Beanmethods, so Spring sees from the method signature what is being created where. You could also try@Orderto influence the order creation, eventually using several@Configurationclasses 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.