We have some service interfaces such as:
public interface MyAdapterService { void doSomething();}
And consumers, which work on all registered instances of this service:
@Service@RequiredArgsConstructorpublic 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@AllArgsConstructorpublic 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:
@Configurationpublic 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
:
@Configurationpublic 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 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:
@AllArgsConstructorpublic class MyOtherXAdapterService implements MyAdapterService{ private final MyDependency myDependency; @Override public void doSomething() { // do something }}
and
@AllArgsConstructorpublic class MyOtherYAdapterService implements MyAdapterService{ private final MyDependency myDependency; private final MyConfigurationProperties properties; @Override public void doSomething() { }}
and
@AllArgsConstructorpublic class MyWrapperAdapterService implements MyAdapterService{ private final MyAdapterService wrappedAdapterService; @Override public void doSomething() { wrappedAdapterService.doSomething(); }}
with
@Servicepublic class MyDependency { public void doSomethingElse() { // do something else }}
and
@Component@Getter@Setter@ConfigurationProperties("my.config")public class MyConfigurationProperties { private String foo; private String bar;}