Create beans on application load - spring-boot

Before starting to read, that's a list of the links I've tried to read to solve this issue:
Spring : Create beans at runtime
How to add bean instance at runtime in spring WebApplicationContext?
https://www.logicbig.com/tutorials/spring-framework/spring-core/bean-definition.html
Spring - Programmatically generate a set of beans
How do I create beans programmatically in Spring Boot?
I'm using SpringBoot and RabbitMQ for my services and lately, I've read this article: https://programmerfriend.com/rabbit-mq-retry/
I would like to generalize this idea and create a SpringBoot library that can get in the names of the queues that need to be created via the application.properties file and do everything behind the scenes, so the creation of the different queues and the binding between them will happen "automagically" whenever I'll integrate the library into a service.
The problem which I'm facing is basically to have the same behavior #Bean gives me. I need to repeat this behavior multiple N times (as many as the number of names specified in my application.properties).
From what I could see online, there's a possibility to create beans when the app is loaded, but the only way to do it is by telling the context what type of class you want to generate and let Spring handle it itself. as the class I want to generate does not contain a default constructor (and does not controlled by me), I wondered if there's a way to create an object and add this specific instance to the application context.

but the only way to do it is by telling the context what type of class you want to generate and let Spring handle it itself
No; since 5.0 you can now provide a Supplier for the bean.
/**
* Register a bean from the given bean class, using the given supplier for
* obtaining a new instance (typically declared as a lambda expression or
* method reference), optionally customizing its bean definition metadata
* (again typically declared as a lambda expression).
* <p>This method can be overridden to adapt the registration mechanism for
* all {#code registerBean} methods (since they all delegate to this one).
* #param beanName the name of the bean (may be {#code null})
* #param beanClass the class of the bean
* #param supplier a callback for creating an instance of the bean (in case
* of {#code null}, resolving a public constructor to be autowired instead)
* #param customizers one or more callbacks for customizing the factory's
* {#link BeanDefinition}, e.g. setting a lazy-init or primary flag
* #since 5.0
*/
public <T> void registerBean(#Nullable String beanName, Class<T> beanClass,
#Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
So
context.registerBean("fooBean", Foo.class, () -> someInstance);

Related

Injecting scoped beans in the context of one AMQP message in Spring Boot

When writing web application with Spring Boot you can declare your beans to be part of the session- or request scope.
Is it possible to create a scope for injection of data with messages received by a #RabbitListener?
When receiving a message with a the RabbitListener, I would like to initialize some metadata which I would like to have available for injection in code called during message processing. (e.g. receiving queue for logging, or a pre-configured factory where the configuration depends on parameters known at receiving time of the message).
Passing this data through all called methods feels just ugly.
Ways I could think of, but don't know how to do it:
AOP
Custom scopes
Help is greatly appreciated!
Probably the simplest approach would be to use a MessagePostProcessor (add to the container/container factory via the afterReceivePostProcessors property).
/**
* Set {#link MessagePostProcessor}s that will be applied after message reception, before
* invoking the {#link MessageListener}. Often used to decompress data. Processors are invoked in order,
* depending on {#code PriorityOrder}, {#code Order} and finally unordered.
* #param afterReceivePostProcessors the post processor.
* #since 1.4.2
*/
public void setAfterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) {
Assert.notNull(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot be null");
Assert.noNullElements(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot have null elements");
this.afterReceivePostProcessors = MessagePostProcessorUtils.sort(Arrays.asList(afterReceivePostProcessors));
}
Use a ThreadLocal to store the metadata; you can then access the metadata in the listener.

Spring Integration MessageBuilder getSequenceDetails class java.lang.String cannot be cast to class java.util.List

I'm using Spring Integration in my Spring Boot project.
My spring boot application gives error like this:
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.List (java.lang.String and java.util.List are in module java.base of loader 'bootstrap')
at org.springframework.integration.support.MessageBuilder.getSequenceDetails(MessageBuilder.java:206)
at org.springframework.integration.support.AbstractIntegrationMessageBuilder.popSequenceDetails(AbstractIntegrationMessageBuilder.java:87)
at org.springframework.integration.support.MessageBuilder.popSequenceDetails(MessageBuilder.java:238)
at org.springframework.integration.support.MessageBuilder.popSequenceDetails(MessageBuilder.java:49)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.completeGroup(AbstractCorrelatingMessageHandler.java:843)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.processMessageForGroup(AbstractCorrelatingMessageHandler.java:498)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.handleMessageInternal(AbstractCorrelatingMessageHandler.java:471)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:170)
... 129 more
I'm not using sequenceDetails header anywhere and It looks like Spring Integrations puts this itself and now gives exception.
I debugged and saw that the sequenceDetails header like this:
sequenceDetails [[67eea8d9-3295-4ea1-b85e-9ef970298986, 0, 0]]
Why do I face this error? Please tell me what you want to see from my project to solve this problem.
It looks like you do some serialization before that aggregator so List<List<Object>> value for IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS header is converted to string somehow. Probably just via simple toString().
First of all you may not populate such a headers information via applySequence=false on the splitter upstream.
Secondly you may disable popping sequence details on the aggregator level.
See its respective property:
/**
* Perform a
* {#link org.springframework.integration.support.MessageBuilder#popSequenceDetails()}
* for output message or not. Default to true. This option removes the sequence
* information added by the nearest upstream component with {#code applySequence=true}
* (for example splitter).
* #param popSequence the boolean flag to use.
* #since 5.1
*/
public void setPopSequence(boolean popSequence) {
Or you can convert that string back to List<List<Object>> from string via some header enricher in between.
It is really unusual to see such a header be converted to string. I'm really curious how that happened in your use-case. Perhaps we may some automation on the framework level if we would know more about your scenario.

Spring Integration Flow: filter based on headers without SpEl expression

I have the following Spring integration flow:
#Bean
public IntegrationFlow checkoutEventFlow(){
return IntegrationFlows.from(EventASink.INPUT)
.filter("headers['type'] == 'TYPE_A'") //1
.transform(Transformers.fromJson(EventA.class)) //2
.<EventA, EventB> transform(eventA ->
new EventB(
eventA.getSomeField(),
eventB.getOtherField()))
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
1) I would like to filter a message based on its headers without using a SpEl expression (look at //1), is it possible?
2) Is there another mechanism for JSON conversion to POJO without //2? I like the way #StreamListener can be written in terms of POJO and conversion is done behind the scenes.
Thanks in advance.
Without SpEL in the filter() you can do a Java lambda instead:
.filter(Message.class, m -> m.getHeaders().get("type") == "TYPE_A")
The Spring Cloud Stream is opinionated Framework where a JSON is as a default content type for data traveling through the flow and beyond into/from the target messaging system.
The Spring Integration is a library to let you build integration applications. There we just can't have any opinion in regards to some default content type conversion. There there is no any out-of-the-box guessing that you are going to transform your incoming byte[] to some POJO because of JSON. Although to honor a bit some possibilities which are visible the same way Spring Cloud Stream does, we have a hook in the POJO method invoker to transform from JSON into expected POJO. But that is done only against custom POJO methods when they also marked with the #serviceActivator. From here we can't assume your expectation in the .transform() lambda. You need to have some service with method and use it in the:
/**
* Populate a {#link ServiceActivatingHandler} for the
* {#link org.springframework.integration.handler.MethodInvokingMessageProcessor}
* to invoke the {#code method} for provided {#code bean} at runtime.
* In addition accept options for the integration endpoint using {#link GenericEndpointSpec}.
* #param service the service object to use.
* #param methodName the method to invoke.
* #return the current {#link IntegrationFlowDefinition}.
*/
public B handle(Object service, String methodName) {
This way it is going to work the same way you see in Spring Cloud Stream with #StreamListener.

Difference between group id, Client id and id in KafkaListener Spring Boot

I am starting to work with Spring Boot 2 and Spring Kafka, I don't quite understand what's the difference between group id, Client id, and id in in KafkaListener interface.
I know group ID is used by Kafka broker to manage multiple Consumer in the same group, but what about the others? what advantage do I get by setting them? where can I see the effect of setting or not setting them?
Based on their java doc :
/**
* The unique identifier of the container managing for this endpoint.
* <p>If none is specified an auto-generated one is provided.
* #return the {#code id} for the container managing for this endpoint.
* #see org.springframework.kafka.config.KafkaListenerEndpointRegistry#getListenerContainer(String)
*/
String id() default "";
/**
* Override the {#code group.id} property for the consumer factory with this value
* for this listener only.
* #return the group id.
* #since 1.3
*/
String groupId() default "";
/**
* When provided, overrides the client id property in the consumer factory
* configuration. A suffix ('-n') is added for each container instance to ensure
* uniqueness when concurrency is used.
* #return the client id prefix.
* #since 2.1.1
*/
String clientIdPrefix() default "";
Your groupId understanding is correct.
The id is like a bean name in Spring Framework. However this one is used in the KafkaListenerEndpointRegistry boundaries only. So, if you need a lifecycle control over the particular KafkaListenerContainer created for the mentioned #KafkaListener, you need to inject KafkaListenerEndpointRegistry and use the mentioned getListenerContainer() for the appropriate id.
The clientIdPrefix is reflection of exact client.id property of the Kafka Consumer:
An id string to pass to the server when making requests. The purpose of this is to be able to track the source of requests beyond just ip/port by allowing a logical application name to be included in server-side request logging.

ConditionalOnClass, why JVM allow the class not in classpath in runtime

After study and using spring boot, I understand the usage and the logic behind the spring boot ConditionalOnClass, my questions are:
why "Since this annotation is parsed by loading class bytecode, it is safe to specify classes here that may ultimately not be on the classpath".
Where is the JVM spec related this?
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClass.java#L39
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {#link #name} attribute.
* #return the classes that must be present
*/
Class<?>[] value() default {};
Because Spring catches ClassNotFoundException.
Check source code - https://github.com/spring-projects/spring-boot/blob/d3c34ee3d1bfd3db4a98678c524e145ef9bca51c/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnClassCondition.java#L218

Resources