avaje-inject
avaje-inject copied to clipboard
Add @Bean(defaultCandidate=false) to match Spring v6.2.0-M1
Refer: https://github.com/spring-projects/spring-framework/commit/a8fb16b47cc64d77ffdd83275aa7038a92cef767
/**
* Is this bean a candidate for getting autowired into some other bean based on
* the plain type, without any further indications such as a qualifier match?
* <p>Default is {@code true}; set this to {@code false} for restricted delegates
* that are supposed to be injectable in certain areas but are not meant to get
* in the way of beans of the same type in other places.
* <p>This is a variation of {@link #autowireCandidate()} which does not disable
* injection in general, just enforces an additional indication such as a qualifier.
* @since 6.2
* @see #autowireCandidate()
*/
boolean defaultCandidate() default true;
Also refer to comment from reddit:
This will finally allow library dependencies to keep Beans they use internally private, so they can't get injected in other code just because the type happens to match.
So I'm 100% sure of this yet.
The Problem
The problem this is looking to address relates to multi-module wiring when there becomes 2 candidates to wire and how the "appropriate" candiate is chosen to be wired.
The typical scenario of this case is where we have something like Jackson ObjectMapper and looking to use it in a module ... and the project is big enough that there is another module that is also looking to use an ObjectMapper and these have different configurations required for each module [and that there are different developers for the modules who are not necessarily aware of each others ObjectMapper].
When we look to wire multiple modules together we now have 2 potential ObjectMapper candidates.
The general solution for this is to use 2 Qualifier names like @Named("Red") @Named("Blue") or @Red @Blue. The implication here is that now every wiring point for ObjectMapper needs to use a qualifier - which is nice and explicit but there is some pain based on the number of wiring points where ObjectMapper is used.
Now, it so happens that CDI works differently here when compared to Spring DI. Lets say we have a factory bean like:
@Red @Bean ObjectMapper myModulesObjectMapper() { ... }
... and we have a component that depends on ObjectMapper (without a qualifier)
@Component
class IUseTheObjectMapper {
@Inject ObjectMapper objectMapper;
}
Now with Spring and Avaje (and Micronaut?) the @Red ObjectMapper is an equal candidate to be injected compared to another ObjectMapper that has a different qualifier (or even no qualifier).
That is, the wiring point does not use a qualifier (give me a candidate regardless of qualifier) ... and so any candidates can be used here equally (candiated with @Red, @Blue or "no qualifier" are all given equal priority to be wired).
This is different to CDI.
CDI works by silently using a @Default qualifier ... so that injection point is actually more like:
@Component
class IUseTheObjectMapper {
@Default @Inject ObjectMapper objectMapper;
}
With CDI this is more "give me a candidate with the @Default qualifier" ... and so the @Red ObjectMapper is not a candidate here - in fact we need a candidate with the silent/invisible @Default qualifier.
Coming back to the scenario with 2 modules and 2 ObjectMappers ... with CDI we technically only need to use qualifiers in one of the modules. With Spring/Avaje/Micronaut? we need to use qualifiers in both modules [well, everywhere].
Spring is using the @Bean(defaultCandidate=true) so that ... we could choose to only use qualifiers in 1 module.
My current take on this is that ... when avaje is determining a candidate to wire when that wiring point has NO QUALIFIER then there are some options:
-
- Make an un-qualified candidate a higher priority that a qualified candidate (I think this will work without negative side effects)
-
- Be like CDI and REQUIRE a un-qualified candidate (So qualified candidates are now not considered even if there is only one of them? Seems harsh and a bit retrograde)
-
- Go with the Spring
@Bean(defaultCandidate=true)approach which is 2 but only selectively being 2 (
- Go with the Spring
avaje-inject is different in that we already have wiring ordering and this means we can look at a potential better solution to this issue in that, at the time a bean is registered we know the source module and at the time a bean is requested for a wiring point we know the current module being wired.
In this sense, it should be possible for avaje-inject to prioritise the wiring of a "bean local to the module" and use that as a tie breaker. In this sense we could have the good situation where developer A working on module A doesn't actually have to care or know about developer B working on module B ... for that case where they both use the same unqualified type (e.g. ObjectMapper).
That is, with avaje-inject we can use the module being wired as a tie-breaker for multiple unqualified wiring candidates.
Resolved via https://github.com/avaje/avaje-inject/pull/557 ... where avaje-inject will prefer to wire a bean that is local to the module.
This approach is available to avaje-inject due to the way it wires in an ordered way and knows the module being wired (the module that supplies a bean and the current module that is requesting a bean).