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

Add a functional way to register a bean [SPR-14832] #19398

Closed
spring-issuemaster opened this issue Oct 21, 2016 · 15 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Oct 21, 2016

Hans Desmet opened SPR-14832 and commented

Add a registerBean method to ApplicationContext which accepts a lambda with which you register as bean.

The following code uses this method to register a bean of a class A and to register a
bean of class B which has a dependency on class A

@Configuration
public class MyConfiguration
  private final ApplicationContext context;
  public MyConfiguration(ApplicationContext context) {
    this.context = context;
    context.registerBean(AClass.class -> new AClass());
    context.registerBean(BClass.class -> new BClass(context.getBean(AClass.class));
  }

Affects: 5.0 M2

Issue Links:

  • #18353 Programmatic bean registration within configuration classes ("is depended on by")
  • #13410 Parallel bean initialization during startup
  • #18273 ResolvableType should support resolving lambda types
  • #18794 @Configuration interface with Java 8 default methods (as a standalone artifact)
  • #19614 Kotlin extension for GenericApplicationContext with registerBean(KClass) variants
  • #19615 Document functional configuration style
  • #18463 Provide registerBean variants based on ResolvableType
  • #19979 Functional bean dependencies tracking
  • #19546 Provide computeIfAbsent-like method on ObjectProvider

1 votes, 8 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 21, 2016

Juergen Hoeller commented

Nice coincidence: This is on our 5.0 roadmap already :-) We have a few related tickets but none as specific this one yet, so I'll schedule it alongside the existing ones.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 14, 2016

Sébastien Deleuze commented

I have a non trivial draft prototype that seems to work pretty well in Kotlin:

context = GenericApplicationContext()
context.environment.addPropertySource("application.properties")
val mongoUri = context.environment.getProperty("mongo.uri")
val mongoDatabase = mongoUri.split("/")[3]

context.registerBean(IfEqHelperSource::class)
context.registerBean("messageSource", Supplier {
    val messageSource = ResourceBundleMessageSource()
    messageSource.setBasename("messages")
    messageSource
})
context.registerBean(Supplier {
    val viewResolver = HandlebarsViewResolver()
    viewResolver.setPrefix("/templates/")
    viewResolver
})
context.registerBean(LoggingEventListener::class)
context.registerBean(Supplier { MongoClients.create(mongoUri) })
context.registerBean(Supplier { ReactiveMongoTemplate(context.getBean(MongoClient::class), mongoDatabase) })
context.registerBean(UserRepository::class)
context.registerBean(UserController::class)
context.registerBean(GlobalController::class)
context.registerBean(TomcatServer::class)
context.refresh()

Java version could be similar with an additional Class parameter, Kotlin can avoid it thanks to reified type parameters, see the implementation in these Kotlin extensions that I will replace by the native Supplier support when available.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 14, 2016

Oliver Drotbohm commented

Looks like this is one more use case where type resolution of lambdas (as asked for in #18273) would come in handy as you'll probably want to be able to write:

context.registerBean(() -> new MyComponent());

and it's hard to tell users why this doesn't work, or requires stating MyComponent.class explicitly again. Maybe a good time to bring up with the JDK team so that they can introduce the necessary bits and pieces to actually make that work (at least for JDK9).

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 14, 2016

Sergei Egorov commented

Could you please use CompletableFuture instead of Supplier? Will make it possible to do semi-parallel beans resolve. Pretty please!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 14, 2016

Sergei Egorov commented

Very naive demo, but I think you'll get the point:
https://gist.github.com/bsideup/294862e3fa276fd1363f0f5026bfee44

Here both Connection and CachingTemplate are expensive beans (think Flyway migration before we return the DB template, maybe some cache warmups or index creation)

Thanks to CompletableFuture API we can build... errr... A very simple graph of the dependencies and resolve them in parallel by keeping the blocking API with .get(timeout) call.

In our apps, the context creation is a very expensive operation and takes a lot of time because we can't easily parallelize the process.
We use some hacks with Proxies, but would be cool to hide it inside the framework.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 16, 2016

Sébastien Deleuze commented

An open question I have is: should the mechanism to provide a bean be a pure function with something like Function<ApplicationContext, T> or is it ok to just access to the ApplicationContext instance or any other defined outside of the lambda, like I do in my Kotlin example.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 17, 2016

Juergen Hoeller commented

I've got the basic mechanism working, with direct Supplier support built right into RootBeanDefinition itself:

GenericApplicationContext ac = new GenericApplicationContext();
ac.registerBeanDefinition("myService", new RootBeanDefinition(MyService.class, () -> new MyService(...));
ac.refresh();

Now the remaining part is convenience methods for registering such beans, not having to deal with RootBeanDefinition, not having to specify a bean name, etc. Let's see what we can do here for M4 still; we'll continue the story with RC1 for sure.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 19, 2016

Sébastien Deleuze commented

I have upgraded the Mix-IT project to use this new mechanism and it works perfectly :-)

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 20, 2016

Juergen Hoeller commented

I'm in the process of adding Supplier support to our AnnotatedBeanDefinitionReader, and two convenience variants to the top-level AnnotationConfigApplicationContext:

  • registerBean(Class, Supplier) - inferring the bean name from a component annotation (lower-cased simple class name by default)
  • registerBean(String, Class, Supplier) - with an explicit bean name given

The remaining bean definition metadata gets derived from annotations on the component class, if any: including the scope, lazy-init flag, primary flag, qualifiers. This is a pretty convenient and pragmatic combination, not requiring extra APIs for those metadata attributes.

I'm also adding variants with a constructor arguments Object... vararg as an alternative to the Supplier argument, augmenting the constructor arguments in the bean definition (just like <constructor-arg> in XML bean definitions). This is useful for annotated constructors as well, with some arguments provided and the rest to be resolved via autowiring. Since those methods are declared as vararg, they also work for simple registration by name or by name plus Class.

Beyond the above, I envision a FunctionalConfigApplicationContext with a builder-like API for non-annotation scenarios. That context variant won't even activate annotation processing by default, being as lean as possible - and at the same time as complete as possible in terms of fine-grained metadata - for pure functional configuration scenarios. A dedicated KotlinApplicationContext variant would be nice as well. However, those are rather RC1 topics.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 20, 2016

Sébastien Deleuze commented

Sounds awesome!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 23, 2016

Juergen Hoeller commented

I've revised the above signatures with a BeanDefinitionCustomizer vararg. This functional callback allows for programmatically setting the lazy-init flag, primary flag, etc which is particularly useful in non-annotation scenarios. So we have four variants with a Supplier argument now:

  • registerBean(Class<T> beanClass, BeanDefinitionCustomizer... customizers)
  • registerBean(String beanName, Class<T> beanClass, BeanDefinitionCustomizer... customizers)
  • registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers)
  • registerBean(String beanName, Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers)

They are actually declared on GenericApplicationContext, not assuming annotated processing at that level and using a simple beanClass.getName() bean name if not specified. AnnotationConfigApplicationContext overrides the behavior of those methods with its annotation configuration assumptions but otherwise supports the exact same registration style.

Given that arrangement, I don't see a need for a FunctionalConfigApplicationContext anymore. With the above registerBean variants and additionally the classic registerBeanDefinition, possibly in combination with a BeanDefinitionBuilder, we got everything we typically need on GenericApplicationContext itself now.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 23, 2016

Juergen Hoeller commented

Let's consider this ticket as done for M4 (aside from potential polishing until next week) and create a new JIRA ticket for a GenericKotlinApplicationContext subclass that extends GenericApplicationContext and provides overloaded registerBean variants with KClass arguments.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 13, 2017

Yevhenii Melnyk commented

Why not doing method which supplier is a subclass of the bean class?
It seems to be a clean way to register implementation for interface. It could look like this:
registerBean(Class<T> beanClass, Supplier<? extends T> supplier, BeanDefinitionCustomizer... customizers)

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 14, 2017

Juergen Hoeller commented

For type checks in advance of singleton creation - and type checks in general for non-singleton beans - the provided Class should be as specific as possible, for bean type matching but in particular also for early checks for specific lifecycle annotations and callback interfaces. This is why we strongly ask for the actually created class to be specified there, matching the Supplier outcome. Do you see a use case where it wouldn't be possible to specify the same concrete class, or where it would be necessary to artifically restrict the class argument to a base class?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 25, 2017

Yevhenii Melnyk commented

The idea of generic bean class has came to me while I was trying to use scala with spring. The registration used stackable traits like this : new ObjectMapper() with ScalaObjectMapper. This means that an anonimous class is created.
It seems this could be useful while making applied applications, for example, by implementing multiple validators with some slight changes.
Maybe my idea relates more to the #18353

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.