Dave Syer opened SPR-16959 and commented
There is a gap in the the functional bean registration features in BeanDefinitionBuilder
and GenericApplicationContext
. Once you register a Supplier
you have committed to provide an instance of the class you register, whereas in a lot of use cases you don't know whether or not you want to provide it until the bean factory is available (e.g. conditional on another instance of the same class being available). I guess the change needs to be in the BeanDefinitionRegistry
interface, for example to support a Predicate<ConditionContext>
as well as the Supplier
. If the interface doesn't change, I suppose returning null
from the Supplier
might be an option, but that seems a bit ugly, and might be too late, since the BeanDefinition
has already been registered at that point.
Issue Links: - #18353 Programmatic bean registration within configuration classes
Comment From: spring-projects-issues
Andy Wilkinson commented
I've been experimenting a bit with conditions in functional bean registration building on top of Framework. It has become apparent that we really need support at the Framework level so that condition evaluation and bean registration can occur at the right time. As Dave alludes to above, the biggest stumbling block at the moment is with bean-based conditions where we'd need a callback at the point where @Configuration
class parsing is performing bean registration. This would, I think, allow a mixture of functional-based and annotation-based conditional registration in the same application context.
As things stand, condition evaluation is tied to the @Configuration
programming model. It feels to me like this could be generalised so that @Configuration
classes are just one source of conditional beans. Rather than driving the registration from ConfigurationClassPostProcessor
it could be driven from something general that calls multiple different types of sources of conditional beans. That might result in a new callback that's specifically intended for functional bean registration, rather than the current approach that typically seems to involve the use of the more general ApplicationContextInitializer
which doesn't have a well-defined lifecycle for when it's called.
The thoughts above have come from some experimentation and prototyping for reducing memory usage and startup time of Boot apps. Functional registration appears to be one way of doing that but we're quite a way short of declaring that it's the way to do it. As such, we (the Boot team) don't have a concrete need for this functionality just yet, although I do think that conditional beans becoming a broader concept, rather than being confined to the annotation-based programming model as they are today, would be generally useful beyond what Boot may or may not need.
Comment From: sdeleuze
Spring Framework 7 introduces a new BeanRegistrar
contract that allows to combine @Conditional
(and variants, likely including Spring Boot ones even if I have not yet tested) and programmatic conditions, see https://github.com/spring-projects/spring-framework/issues/18353#issuecomment-2704653876.
We should explore if more is needed or if that's flexible enough.
Comment From: sdeleuze
@dsyer @rwinch @wilkinsona @philwebb I am now targeting Spring Framework 7.0.0-M4
for the resolution of this issue. I suggest we explore concrete use cases for additional programmatic conditional capabilities in ~~BeanRegistrar
~~ BeanRegistry
and hopefully provide meaningful related APIs that are not covered by the existing Environment
parameter.
For example, we could provide an equivalent of @ConditionalOnClass
with a method like BeanRegistry#isClassPresent(String)
that would use internally the bean class loader to perform such check. Native support would be possible via https://github.com/spring-projects/spring-framework/issues/34564.
That does not mean we will add support for all current annotation conditions support at Spring Boot level. For example, for now we don't necessarily plan to add support for an equivalent of @ConditionalOnBean
at Spring Framework level, but maybe using @ConditionalOnBean
on the @Configuration
class that imports the bean registrar will be good enough (note I have not yet tested it works).
The most challenging limitation we will have to handle is that BeanRegistrar#register
should not get (by design) arbitrary bean outside of a bean instance supplier. That probably means we will have to support more narrow use cases for the use cases we have like the "configuration from a DSL as described by Rob in the original https://github.com/spring-projects/spring-framework/issues/18353 description. I am currently not sure if and how we will be able to support that, but we can identify and explore those use cases and see if we can find a reasonable tradeoff between what BeanRegistrar
can support in both AOT and non-AOT mode, and the need on usage side (either application, Spring Boot or portfolio level like in Spring Security).
Comment From: philwebb
I'm not sure that BeanRegistrar
would be the best place for a isClassPresent
method, I think perhaps I'd look at adding something to BeanRegistry.Spec
since it feels like conditions are part predicates on the spec to determine if the bean definitions get added or not.
Perhaps something like
Spec.condition(Pedicate<ConditionContext> predicate)
where ConditionContext
could provide access to thing like the bean classloader etc.
Comment From: sdeleuze
My mistake, I meant BeanRegistry
not BeanRegistrar
(now fixed in my comment).
Comment From: sdeleuze
So we discussed the Spring Security DSL use case with @rwinch who proposed a really interesting idea where Spring Security DSL could be used inside a BeanRegistrar
implementation written by the user. I refined it a bit with an abstract class that Spring Security could provide, and that gives something like below which is already supported in what has been shipped in Spring Framework 7.0.0-M3
and does not require additional conditions. cc @wakingrufus @OlgaMaciaszek @rstoyanchev @dsyer
@Configuration
@Import(MySecurity.class)
class MyConfiguration {
}
class MySecurity extends SecurityRegistrar {
@Override
public void register(HttpSecurity registry, Environment env) {
http.formLogin();
}
}
// Could be provided by Spring Security
abstract class SecurityRegistrar implements BeanRegistrar {
public abstract void register(HttpSecurity http, Environment env);
@Override
public void register(BeanRegistry registry, Environment env) {
HttpSecurity http = new HttpSecurity();
register(http, env);
if (http.hasFormLogin()) {
registry.registerBean(...);
}
// ...
}
}
For other conditions, as discussed recently we would prefer to give more time post Framework 7.0 / Boot 4.0 GA to the Spring Boot team to provide feedback on potential programmatic equivalent of @ConditionalOnBean
, @ConditionalOnMissingBean
and similar advanced capabilities.
For classpath checks, it is unclear if we should let users just use if statements with ClassUtils#isPresent
, if we should expose a ClassLoader
or if we should provide something like BeanRegistry#isClassPresent(String)
. As we don't have a clear use case for now, I am leaning to wait for Spring Boot team feedback post GA as well.
As a consequence, I move back this issue to the general backlog for potential exploration in Spring Framework 7.x after the 7.0 GA in collaboration with the Boot team. Does not prevent to add specific capabilities before 7.0.0 if some very well defined needs arise via more specific issues.
Comment From: OlgaMaciaszek
The ability to work with DSL looks very interesting and might help us to work around the lifecycle issues for users interacting directly with Interface Clients Registry (CC @rstoyanchev ). Will try it out and provide any feedback that we have.