Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programmatic bean registration with configuration classes #18353

Closed
spring-projects-issues opened this issue Dec 8, 2015 · 53 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Dec 8, 2015

Rob Winch opened SPR-13779 and commented

It would be nice to be able to allow Java Configuration to register multiple types of Beans. For example, right now the Spring Security exposes a Java DSL like this:

public void configure(HttpSecurity http) {
    http
        .formLogin()
}

This single invocation (made by the developer configuring Spring Security) should ideally create numerous Beans (i.e. UsernamePasswordAuthenticationFilter, AuthenticationEntryPoint, etc) and expose them to the Spring ApplicationContext.

The key takeaway is that a developer should be able to interact with a DSL where a single invocation creates multiple Beans.

This is something Juergen Hoeller and I spoke about briefly at SpringOne that I would like to get on the roadmap (hopefully for Spring 5).

Updated

To elaborate on my comment below, I think it would be nice if we could do something like this:

class MyDsl {
   private boolean addABean;
   private boolean addBBean;
   // getters /setters
}

class MyDslXmlParser {
   MyDsl parse(Document d) {
      return createDls(d);
   }
}

class MyDslParser {
    public void registerBeans(MyDsl dsl, BeanFactory) {
        if(dsl.isAddABean()) {
            bf.registerBean(new A());
        }
        if(dsl.isAddBBean()) {
            bf.registerBean(new B());
        }
    }
}

I Java Config Users could consume this with:

class JavaConfig {
    @Bean
    public MyDsl myDsl() {
        MyDsl myDsl = new MyDsl();
        myDsl.setAddABean(true);
        return myDsl;
    }
}

and MyDslParser.registerBeans would automatically be invoked with the proper arguments.

In XML Config users could consume this with:

<mydsl:mydsl aBean="true" />

and MyDslParser.registerBeans would automatically be invoked with the proper arguments.

This would allow the framework to easily support multiple ways of configuring the Beans.


Issue Links:

2 votes, 16 watchers

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Rob Winch, could you sketch the bean registration logic for a use case such as the above? What would it take to register those underlying beans via the BeanDefinitionRegistry, i.e. via GenericBeanDefinition setup and registerBeanDefinition calls? Ideally, I'd like to provide something more lambda-oriented than that but for a start it'd be good to understand your needs a bit better.

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

Juergen Hoeller Thanks for reaching out.

I have put together a small (very simplified) sample that demonstrates the use case above. Some of the simplifications are:

  • There are hard coded values (i.e. the user is hard coded)
  • Not a lot of thought went into code organization, naming, etc. This is only meant to demonstrate the goals.
  • There are a lot of missing beans from what would normally be present. For example, at this time I do not create the servlet Filter that performs authorization. There are quite a few other Bean Definitions that I do not create.

Ideally, I'd like to provide something more lambda-oriented than that but for a start it'd be good to understand your needs a bit better.

Part of the reason I like the idea of using BeanDefinition s is because they tend to handle circular references better. This will almost certainly improve the user experience when they are using Spring Security since it tends to cause all sorts of circular references. An example of such:

  • Spring Security's method Security is applied to Spring Data Repositories
  • Spring Security's method Security needs to use an AuthenticationManager
  • The user provides a custom implementation of AuthenticationManager that is backed by Spring Data

Ultimately, I think it would be awesome if somehow I could reuse the Bean Creation logic for both my XML Namespace and Java Configuration. For example, I might first turn the following XML:

<http>
   <form-login/>
</http>

into a Java Bean like:

HttpSecurity http = new HttpSecurity();
http
	.formLogin();

Then I can run the HttpSecurity object through the same logic that creates Beans from Java Config DSL (i.e. the code that creates beans from the HttpSecurity object).

Cheers,
Rob

@spring-projects-issues
Copy link
Collaborator Author

Janne Valkealahti commented

I think these issues for getting proper programmatic registration of beans can be boiled down to a very simple missing feature, from JavaConfig returning a list of beans.

At compile time if I don't know how many instances of MyBean class I have, I either have to use ImportBeanDefinitionRegistrar which most of a times is a bit useless as it can only access annotation info and some resources or use BFPP's. So many times I've hoped that I could just return List and spring would treat list members as beans.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

We have these cases in the MVC Java config:

  1. ViewResolver and HandlerExceptionResolver beans -- either a default set or the set of instances provided by the application through a WebMvcConfigurer. Currently we use a ViewResolverComposite and a HandlerExceptionResolverComposite to wrap these sets but it's not ideal with regards to lifecycle methods since we can't be sure if given instances are already beans or not.

  2. Optional registration of a HandlerMapping depending on static resource and view controller registrations via a WebMvcConfigurer.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

See also my functional Spring Boot draft proposal since I think this use case could take advantage of what is discussed here.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

So is there anything that we need to do for 5.0 still? If yes, could the stakeholders please summarize their current position :-)

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

Juergen Hoeller Thanks for reaching out. I chatted with Sébastien Deleuze I don't think this is really solved from my perspective. He is going to see if he can prototype out the example I have above and get back to me.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

We had a discussion with Rob about his use case. Functional bean registration API is very powerful because it allows to register programmatically beans, using if or for statements, but maybe the missing point is how to integrate properly in a JavaConfig based Spring application (typically a Spring Boot one).

The most important need I have identified about the feature discussed here is that Spring Framework should provide a way to contribute some beans with the functional bean registration API as part of an application that is using XML or JavaConfig, and I am not sure actually how to do that in order to get it invoked at the right moment of the lifecycle.

To express that differently, the need here is to allow Spring Security and other Spring projects to leverage the powerful/flexible bean registration API for there internals while integrating in Spring Boot application that still leverage JavaConfig for users beans or Spring Boot internals. So it seems to me that we need to have a bridge between JavaConfig and functional bean rehgistration API to use both in the same application. That would be super useful for Spring Boot as well (discussion on this issue shows that there is no easy way to do that currently).

Another important point is how the MyDsl object will be provided. If we take Spring Securitry example, the DSL is what the user provides using @EnableWebSecurity + WebSecurityConfigurerAdapter overriden methods. If we take a concrete example, currently Spring Security allows to specify its configuration via a Java DSL that leverage internally @Import to create a few beans + META-INF/spring.factories to create object instances that are not beans because of the current limitation of JavaConfig:

@Configuration
@EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().hasRole("USER")
                .and()
            // Possibly more configuration ...
            .formLogin() // enable form based log in
                // set permitAll for all URLs associated with Form Login
               .permitAll();
    }

    @Bean
    public Foo fooBean() { ... }

    @Bean
    public Bar barBean(Foo fooBean) { ... }
 
}

The purpose of the feature discussed on this issue would be IMO to provide a way for Spring Security to provide a registerBeansWithFunctionalApi method that could invoke something like configure(HttpSecurity http) to allow the user to specify his configuration using the Java DSL, and then to perform various context.registerBean invocations to register beans consitionnaly based on what the user has register.

For example, it would be nice for Spring Security to be able to do that kind of things:

public class WebSecurityConfigurerAdapter {
	
	@FunctionalBeanRegistration
	public void registerBeansWithFunctionalApi(GenericApplicationContext context) {
		HttpSecurity http = configure(new HttpSecurity());
		HttpDsl httpDsl = http.generateDsl();
	    	if (httpDsl.isAddFooBean()) {
			context.registerBean(Foo.class);
			if (httpDsl.isAddBarBean()) {
				context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));
			}
		}
	}	
}

I am not sure at all there is a need for a dedicated annotation for that, but the idea is to provide an extension point that can allow a JavaConfig Spring application to leverage functional bean registration API. Instantiating ApplicationContext and calling refresh() would still be manage by JavaConfig.

@spring-projects-issues
Copy link
Collaborator Author

Janne Valkealahti commented

One of the easiest examples to show what we're missing from a programmatic registration is how Spring Integration javadsl fails. If taking below example which creates gateway and registers it to app context, you can't ever auto-wire it because only hook they have is registerSingleton and that is called only after spring tries to auto-wire beans.

@Bean
public IntegrationFlow iotGatewayFlow() {
  return IntegrationFlows
    .from(MyGatewayInterface.class)
    .get();
}

IntegrationFlowBeanPostProcessor.java#L283

this.beanFactory.registerSingleton(beanName, component);

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've done some local tests with straight use of an injected GenericApplicationContext, and this seems to work fine for me...

@Configuration
public class MyConfigClass {

    @Autowired
    public void register(GenericApplicationContext ctx) {
        ctx.registerBean(...);
    }

    @Bean
    public MyOtherBean() {
        ....
    }
}

Anything I'm missing here?

@spring-projects-issues
Copy link
Collaborator Author

Janne Valkealahti commented

I've never seen any of our own code to directly use GenericApplicationContext, probably for a good reason as I'd assume it opens a can of worms to all sort of other issues which are potentially impossible to track down.

Lets say that there are multiple @Configuration classes which register their own MyOtherBean's and then some other class injects List. I assume there is no way for Spring to know that those specific register methods should be called before possible inject happens for those beans. In this case I assume what would get injected to List is not predictable?

Would it be bad to have some sort of an annotation which would instruct context that this specific method will eventually provide/register beans of certain type? Not sure I like this idea myself either but we do have a chicken/egg situation here and all these are really starting to limit what we can do in all other Spring umbrella projects.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Juergen Hoeller I made a try with my MiXiT application (which is a Spring Boot + Kotlin application), if I replace

@SpringBootApplication
@EnableConfigurationProperties(MixitProperties::class)
class MixitApplication {

    @Bean
    fun viewResolver(messageSource: MessageSource, properties: MixitProperties) = MustacheViewResolver().apply {
        val prefix = "classpath:/templates/"
        val suffix = ".mustache"
        val loader = MustacheResourceTemplateLoader(prefix, suffix)
        setPrefix(prefix)
        setSuffix(suffix)
        setCompiler(Mustache.compiler().escapeHTML(false).withLoader(loader))
    }

    @Bean
    fun filter(properties: MixitProperties) = MixitWebFilter(properties)

    @Bean
    fun markdownConverter() = MarkdownConverter()
}

By

@SpringBootApplication
@EnableConfigurationProperties(MixitProperties::class)
class MixitApplication {

    @Autowired
    fun register(ctx: GenericApplicationContext) {
        ctx.registerBean {
            MustacheViewResolver().apply {
                val prefix = "classpath:/templates/"
                val suffix = ".mustache"
                val loader = MustacheResourceTemplateLoader(prefix, suffix)
                setPrefix(prefix)
                setSuffix(suffix)
                setCompiler(Mustache.compiler().escapeHTML(false).withLoader(loader))
            }
        }
        ctx.registerBean<MixitWebFilter>()
        ctx.registerBean<MarkdownConverter>()
    }
}

I get the following error :

Parameter 2 of constructor in mixit.web.handler.BlogHandler required a bean of type 'mixit.util.MarkdownConverter' that could not be found.
Action:
Consider defining a bean of type 'mixit.util.MarkdownConverter' in your configuration.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Janne Valkealahti, Sébastien Deleuze, good points: Such @Autowired-driven registrations work in general but they might come in too late for other injection points. I'll see what we can do about this, probably enforcing such a callback at BeanDefinitionRegistryPostProcessor time.

@spring-projects-issues
Copy link
Collaborator Author

Sébastien Deleuze commented

Here is a quick update on the Spring Boot + functional bean registration use case : in addition to @Autowired-driven registration, the other way to register beans with Boot is via using ApplicationContextInitializer with SpringApplication API, as described in this comment.

When this issue will be fixed, I will check both works with MiXiT application.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I still don't have a clear enough vision of a dedicated first-class mechanism here, so I'd rather defer this to 5.1. The existing mechanisms remain in place: functional registration works in custom BeanDefinitionRegistryPostProcessor and ApplicationContextInitializer implementations which can be mixed and matched with configuration classes. There is just no specific callback arrangement for functional registration within configuration classes yet.

@sdeleuze
Copy link
Contributor

I would like to provide an updated POV on that issue based on the use cases we see on Spring Native side and based on latest @jhoeller feedback.

While working on native support, we have seen some consistent patterns emerging and requiring manual native configuration because reflection based. In most cases, using more functional constructs allows native-image compiler to include automatically the required code via pure static analysis.

The pattern we see is typically for advanced configuration of let say Spring Security or Spring Data where regular @Configuration are not dynamic enough. I think there are 2 complementary ways to solve that:

  • Include @ConditionalOnMissingBean and @ConditionalOnClass from Spring Boot [SPR-11296] #15920 to provide more flexibility at Framework level (so usable directly by Spring Security or Spring Data) in a declarative fashion
  • This issue where we potentially could introduce a dedicated functional bean API that would be used to replace/evolve ImportSelector (by design very reflection oriented with its List<String> return type), BeanDefinitionRegistryPostProcessor and ImportBeanDefinitionRegistrar (here the functional variant would be conceptually the same than the beanDefinition based API but exposed with a lambda style that could be seen as a natural Java 8+ based evolution, with better native compatibility).

As pointed out by Juergen, it is currently already theoretically possible by casting BeanDefinitionRegistry to GenericApplicationContext. Also:

At the moment, supplier-based registration works everywhere via a GenericBeanDefinition and setInstanceSupplier, then passed to plain BeanDefinitionRegistry.registerBeanDefinition

So a potential outcome of this issue could be a dedicated functional contract (like the registerBean methods on GenericApplicationContext) that could be triggered from configuration classes, in order to provide more guidance (with related documentation) and discoverability to projects like Spring Data, Spring Security or even third party ones.

It could be done via @EnableFoo annotations and their related imports, and transformed to a more programmatic approach by a build time transformation for native needs. I am not sure yet there is a need to allow that from within configuration class, but to be discussed.

cc @aclement @dsyer @bclozel @rwinch @mp911de @christophstrobl

@mp911de
Copy link
Member

mp911de commented Jan 28, 2021

Spring Data's repository bean registrations make use of BeanDefinitionBuilder and BeanDefinitionRegistry.registerBeanDefinition(…) to register beans. We attempt also to delay class initialization to avoid loading classes unless required as eager class loading may interfere with AOP (specifically EclipseLink) or when using different classloaders.

In terms of reflection, we have few types (e.g. JpaRepositoryFactoryBean, EntityManagerBeanDefinitionRegistrarPostProcessor, JpaMetamodelMappingContextFactoryBean, PersistenceAnnotationBeanPostProcessor, `repository fragments) that are affected. Since repository fragments require reflection then from a Spring Data perspective we could optimize away.

We use import selectors also for e.g. @EnableJpaAuditing to obtain the annotation metadata and configure based on the annotation attributes how the beans get instantiated and things like autowireMode. Auditing is a pretty static arrangement with a static number of beans to register.

@sdeleuze sdeleuze modified the milestones: 7.0.x, 7.0.0-M3 Feb 24, 2025
@sdeleuze sdeleuze changed the title Programmatic bean registration within configuration classes Programmatic bean registration with configuration classes Feb 25, 2025
@dsyer
Copy link
Member

dsyer commented Feb 25, 2025

I tried it and found that there was no way to set attributes on the registered bean definitions (with a BDRPP and the GenericApplicationContext you can add a bean definition postprocessor). Can we add that somehow? Or at least another way to make the functional bean definitions not fail on AOT processing?

@wakingrufus
Copy link
Contributor

This is looking great @sdeleuze !
A couple questions:
Is there a significant performance difference between using an ApplicationContextInitializer with spring.factories vs an Application class (no autoconfiguration) which @Imports a Configuration class which implements this new interface?
Also, is there a plan to allow IDEs to detect programmatically registered beans? I know spring generates an environment properties metadata file. Is there something similar for beans?

@anbusampath
Copy link

BeanRegistrar is nice approach. Do we need to keep below the code block always?


public static class Init implements InitializingBean {

	public boolean initialized = false;

	@Override
	public void afterPropertiesSet() throws Exception {
		initialized = true;
	}
}

BeanRegistrars are now referenced from @Configuration classes with @Import.**
BeanRegistrar feels like a natural good fit for @Import

is it additional to previously suggested @configuration which implements BeanRegistrar?

@sdeleuze
Copy link
Contributor

sdeleuze commented Feb 25, 2025

@dsyer For attributes, I think the design principles of BeanRegistar would favor exposing higher level features rather han directly exposing attributes. Order support is a good example of that. For Spring AOT, I am working on adding such support, should be available shortly. Do you have other needs?

@wakingrufus With #34486 and potential additional optimizations we are going to do, should be very close and should provide better Spring AOT / GraalVM native support, so I would recommend to consider using it for Spring Fu-like use cases. For IDEs, hat's a good point, I plan to discuss it with IntelliJ IDEA team and I will be happy to sync with @martinlippert for Eclipse / VS code.

@anbusampath It is not additional to the previously suggested @Configuration which implements BeanRegistrar, it supersedes it. Not sure to understand your question about "Do we need to keep below the code block always", this is just an example of a bean implementing a "special Spring interface".

@anbusampath
Copy link

Not sure to understand your question about "Do we need to keep below the code block always", this is just an example of a bean implemented a "special Spring interface".

I meant InitializingBean is mandatory to make bean registration work with BeanRegistrar. Now I got it, it is additional bean implementation(optional).

@dsyer
Copy link
Member

dsyer commented Feb 25, 2025

BeanRegistar would favor exposing higher level features rather than directly exposing attributes

I can see why you might want to do that, but I still want a BeanDefinitionCustomizer.

@sdeleuze
Copy link
Contributor

Yes, I am on it, I should not have mixed the bean customizer and the instance supplier. I will fix it shortly.

@sdeleuze
Copy link
Contributor

I just pushed the related modifications and updated the related comment above. Will add more tests to check the behavior before end of the week.

@wakingrufus
Copy link
Contributor

Is the plan to have BeanRegistrarDsl be source-compatible with BeanDefintionDsl? or will there have to be a migration to switch?

@sdeleuze
Copy link
Contributor

Migration will be required (but easy, both are pretty close).

@wakingrufus
Copy link
Contributor

Migration will be required (but easy, both are pretty close).

Ok thanks. I'd be happy to help with an openrewrite recipe when the time comes.

sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Mar 6, 2025
This commit introduces a new BeanRegistrar interface that can be
implemented to register beans programmatically in a concise and
flexible way.

Those bean registrar implementations are typically imported with
an `@Import` annotation on `@Configuration` classes.

See BeanRegistrarConfigurationTests for a concrete example.

See spring-projectsgh-18353
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Mar 6, 2025
This commit introduces a new BeanRegistrarDsl that supersedes
BeanDefinitionDsl which is now deprecated.

See BeanRegistrarDslConfigurationTests for a concrete example.

See spring-projectsgh-18353
@sdeleuze
Copy link
Contributor

sdeleuze commented Mar 6, 2025

After a lot of refinements and polish (thanks @jhoeller for your feedback), I have merged this feature that I consider as a first-class support for programmatic bean registration, including with support for Spring AOT optimizations and GraalVM native images. I will likely refine the AOT support in M4 to support more advanced use cases.

See the related reference documentation for more details.

@rwinch As discussed, your original use case of conditional bean registration depending on another bean/DSL is not yet supported, but let's explore what we can do to help you for this use case, I suggest we continue this exploration as part of #21497.

@wakingrufus
Copy link
Contributor

🎉 so happy to see this done!
I included a slide showing usage of this new API in my talk yesterday at DevNexus.

@martinlippert
Copy link
Member

@wakingrufus With #34486 and potential additional optimizations we are going to do, should be very close and should provide better Spring AOT / GraalVM native support, so I would recommend to consider using it for Spring Fu-like use cases. For IDEs, hat's a good point, I plan to discuss it with IntelliJ IDEA team and I will be happy to sync with @martinlippert for Eclipse / VS code.

Support for this in the Spring Tools for VSCode and Eclipse is underway via spring-projects/spring-tools#1498 and spring-projects/spring-tools#1499.

@OlgaMaciaszek
Copy link
Contributor

This looks great @sdeleuze. From interface clients autoConfiguration perspective, the ability to also add bean aliases, as currently supported through BeanDefinitionHolder would also be important. Could adding this be considered?

@sdeleuze
Copy link
Contributor

Glad you like it. Should be possible, could you please create a related issue? I will add this in M4.

@OlgaMaciaszek
Copy link
Contributor

@sdeleuze here it is: #34599

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests