Spring event notify all scoped beans possible? - spring

The Spring event mechanism supports publishing application events and listening to these events. as it is explained in this question: Scoped Spring events possible? spring event notify only the current request session listeners. so my question is that possible to notify all existent scoped beans.
code example:
#Controller
public class FooController {
#Autowired
private ApplicationEventPublisher publisher;
#GetMapping("/fireEvent")
public void notifyAllScopedBeans() {
publisher.publishEvent(new FooEvent(this));
}
}
#SessionScope
#Component
public class FooListener {
private String username = "this bean username"
#EventListener(FooEvent.class)
public void listen() {
System.out.println("I'm listening. PS : I am
"+this.username);
}
}

Related

Spring Cloud Stream - First Kafka messages get error "Dispatcher has no subscribers"

My app successfully sends Kafka messages, but only after Kafka is initialized. Before that i get the error "Dispatcher has no subscribers". How do i wait for subscribers to finish being registered for channels?
Here's a trace of the order of events (timing in second.ms):
17.165 SenderClass created
17.816 initialization class, #PostConstruct starts PollingTask
24.781 PollingTask sends first Kafka message
24.816 First error: "Dispatcher has no subscribers"
25.778 Registering MessageChannel my-channel
still seeing Dispatcher errors
27.067 Channel my-channel' has 1 subscriber
No more errors after this, messages send fine
i'm not sure how to approach this. Wild guesses have included:
Place sending code in #PostConstruct
Add #AutoConfigureBefore(BindingServiceConfiguration.class) to Sender
Add #AutoConfigureAfter(BindingServiceConfiguration.class) to SenderClass
Add #AutoConfigureBefore(BindingServiceConfiguration.class) to Main
Place #DependsOn({"EnableBindingClass"}) on Task
Place #DependsOn({"ApplicationLifeCycle"}) on SchedulerClass, where ApplicationLifeCycle is a class that does nothing but
implements SmartLifecycle with getPhase returning MAX_INT
Making sure ComponentScan is on for whole package (a suggestion from other SO threads)
Various combinations of the above
Created a new app, made it as simple as i could:
public interface Source {
#Output(channelName)
MessageChannel outboundChannel();
}
#EnableBinding(Source.class)
#Component
public class Sender {
#Autowired
private Source source;
public boolean send(SomeObject object) {
return source.outboundChannel().send(MessageBuilder.withPayload(object).build());
}
#Service
public class Scheduler {
#Autowired
Sender sender;
#Autowired
ThreadPoolTaskScheduler taskScheduler;
#PostConstruct
public void initialize() {
taskScheduler.schedule(new PollingTask(), nextTime);
}
private class PollingTask implements Runnable {
#Override
public void run() {
List<SomeObject> objects = getDummyData();
for(SomeObject object : objects)
{
sender.send(interval);
}
Instant nextTime = Instant.now().plusMillis(1_000L);
try {
taskScheduler.schedule(new PollingTask(), nextTime);
} catch (Exception e) {
logger.error(e);
}
}
}
Edit to add Solution
It works now! In my scheduler that starts the things that send the messages i switched from starting things in #PostConstruct to SmartLifecycle::start().
#Service
public class Scheduler implements SmartLifecycle {
#Autowired
Sender sender;
#Autowired
ThreadPoolTaskScheduler taskScheduler;
#Override
public void start() {
taskScheduler.schedule(new PollingTask(), nextTime);
}
private class PollingTask implements Runnable {
#Override
public void run() {
List<SomeObject> objects = getDummyData();
for(SomeObject object : objects)
{
sender.send(interval);
}
Instant nextTime = Instant.now().plusMillis(1_000L);
try {
taskScheduler.schedule(new PollingTask(), nextTime);
} catch (Exception e) {
logger.error(e);
}
}
}
#PostConstruct is too early to send messages; the context is still being built.. Implememt SmartLifecycle, put the bean in a high phase (Integer.MAX_VALUE) and do the sends in start().
Or do the sends in an ApplicationRunner.
I faced a similar problem in Webflux + Spring Cloud Stream functional style. Spring Cloud Function in 2022 is the preferred way. ​
My hypothesis after a lot of debugging was that beans were not created in right order. The bean was probably not registered in spring-cloud-stream's dispatchers before kafka message processing started. similar to what #gary mentioned.
So I added #Order(1) before my consumer beans. Hoping that this bean would be created before it is dispatcher-registrations starts.
​#Bean
​#Order(1)
​public Function<Flux<Message<Pojo>>, Mono<Void>> pojoConsumer() {
This seems to fix my issue for now.

Propagate Spring boot security context into a bean annotated with #KafkaListener

I have a bean that is annotated with #KafkaListener and inside this bean, I am planning to get the logged-in user credentials through SecurityContextHolder.
However, SecurityContextHolder.getContext().getAuthentication() is giving me a null object probably because this is running in a different thread.
In this case, is there a way to propagate the SecurityContext from ThreadLocal to another thread? Can it easily be done in my Spring Boot configuration?
Below is the sample code:
#Component
#Slf4j
public class MessageConsumer {
private final MessageService messageService;
#Autowired
public MessageConsumer(final MessageService service) {
messageService = service;
}
#KafkaListener(topics = "myTopic")
public void receive(final List<Message> message) {
messageService.consumerAnStoreMessage(message, SecurityContextHolder.getContext().getAuthentication());
}
}
The SecurityContext's authentication represents, for exemple, a user who called a webservice, a website's page... etc...
When you listen to a kafka message, which user's context should be used ?
I don't think what you are trying to do really makes sens.

push a dynamic message in Scheduler websocket using spring boot using stomp

I try to make a chatbot using springboot (websocket), i want to know if it's possible to push a dynamic message in Scheduler, and i need some help, i'can't fugure it out.
I want to push the message in the Scheduler Configure how could i do that:
#EnableScheduling
#Configuration
public class SchedulerConfig {
#Autowired
SimpMessagingTemplate template;
#Scheduled(fixedDelay = 3000)
public void sendAdhocMessages() {
template.convertAndSend("/topic/user", new UserResponse("Fixed Delay Scheduler"));
}
}
in the sendAdhocMessages method i want to pass a message that will be displayed in an html page. in the Official doc it's impossible to pass a parameter to a method which is annotated by #Scheduled, is there any methd to do that?
The official documentation contains a hint to, how you could pass values to the scheduled method. Maybe you could provide a bean that acts as a message provider. In the scheduler class you autowire the message provider and request the messages.
A short code example:
#Componet
public class MessageProvider {
private String message;
// getter and setter ...
}
In the scheduler you could use the message provider like following:
#EnableScheduling
#Configuration
public class SchedulerConfig {
#Autowired
SimpMessagingTemplate template;
#Autowired
MessageProvider messageProvider;
#Scheduled(fixedDelay = 3000)
public void sendAdhocMessages() {
String currentMessage = messageProvider.getMessage();
template.convertAndSend("/topic/user", new UserResponse(currentMessage));
}
}

Spring Event and Scope request

I would like to use Spring Event to "speak" with my beans in my web application.
So, for example, my bean which fires event is like this:
#Controller
#Scope("request")
#KeepAlive
public class Controller extends InitializingBean, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void test() {
applicationEventPublisher.publishEvent(new TestEvent(this));
}
}
And my listener event is like this:
#Component
#Scope("request")
#KeepAlive
public class Module implements ApplicationListener<TestEvent> {
#Override
public void onApplicationEvent(TestEvent event) {
}
}
The most important point is these beans are scope request because they need to be initialized at every time the page is called.
But in startup, I get this message:
Caused by: java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual
web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
Like if Spring try to instantiate my Module bean in startup and as the bean is scope request, it can't do this (the context request is not instantiate)
If I delete event management, everything works fine.
So, my question is:
Is it possible to have event listener is scope request ? And how to do this ?
Thanks
Try to inject a scoped proxy in a Singleton ApplicationListener to handle the TestEvent.
#Scope(proxyMode=ScopedProxyMode.TARGET_CLASS, value="request")
public class TestEventHandler {
public void onTestEvent(TestEvent event)
// ...
}
}
public class TestEventApplicationListener implements ApplicationListener<TestEvent> {
#Autowired
private TestEventHandler handler;
#Override
public void onApplicationEvent(TestEvent event) {
handler.onTestEvent(event);
}
}

How to do integration test with activemq and spring3?

I have a code like this, but I'm not sure how would I test this piece of of code that I extracted from my project. I'm using Spring3 and ActiveMQ. And I'm using spring to do remote HTTPInvoker that's why I have the GateWay. So, when I call method submit in my Gateway, it's going to send a JMS message via JMSDispatcher. How would you inject JmsTemplate to Gateway? As far as I know, if I want to test JMS I have to configure it in application-context.xml in Spring and inject overridden JmsTemplate. So, I could test the message inside the queue? But I can't inject JmsTemplate to Gateway since Mockito will complain about not having that field inside Gateway.
public class Gateway {
#Autowired
private ProcessController processController;
public void submit() {
processControllerFactory.submit();
}
}
public ProcessController {
#Autowired
private JMSDispatcher jmsDispatcher;
public void submit() {
// do something
jmsDispatcher.send(message);
}
}
public JMSDispatcher {
#Autowired
#Qualifier("someJmsTemplate")
private JmsTemplate jmsTemplate;
public void send(MessageCreator message) {
jmsTemplate.send(message);
}
}

Resources