I'm a spring user. and I start to read the source code of spring.
when I read AbstractApplicationContext, I found there's one method start(), I found that the method doesn't be called when ApplicationContext is initialized.
My questions:
1)what the usage of the method? according to the word's(start) meaning, I think it should be called before the ApplicationContext can work. but it doesn't.
2)how can I listen the event which applicationContext starting working? after reading the code, I found the method will publish ContextStartedEvent. but if I just initialize the context, the context still can work and don't publish event.I can't listen the event to track the start of applicationcontext.
The start method is part of the Lifecycle interface, which is called as part of the application startup process.
If you want to be notified when the context is starting you should declare a bean that implements the Lifecycle interface.
public class org.example.MyLifecycle implements Lifecycle {
private boolean started = false;
public boolean isRunning() {
return started;
}
public void start() {
System.err.println("MyLifecycle starting");
started = true;
}
public void stop() {
System.err.println("MyLifecycle stopping");
started = false;
}
}
Then
<bean class="org.example.MyLifecycle"/>
This is all handled, by default, by DefaultLifecycleProcessor unless there's a bean in the context called lifecycleProcessor which implements the LifecycleProcessor interface
Related
My Spring Boot application is subscribing to an event via RabbitMQ.
Another web application is responsible for publishing the event to the queue which my application is listening to.
The event basically contains institute information.
The main application class implements CommandLineRunner and overrides run() method.
This run() method invokes a method to create admin user.
When my application is started, and when an event is already present in queue, the listener in my application is supposed to update the Admin user's institute id.
However it looks like createAdmin() and the listener() are getting executed in parallel and institute id is never updated. Help me in understanding the control flow.
See below the code snippet and the order of print statements.
#SpringBootApplication
public class UserManagementApplication implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(UserManagementApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
createAdmin();
}
private void createAdmin() {
System.out.println("************** createAdmin invoked *********************");
Optional<AppUserEntity> user = appUserService.getUserByUserName("superuser");
if(!user.isPresent()) {
AppUserEntity superuser = new AppUserEntity();
superuser.setUsername("superuser");
superuser.setAppUserRole(AppUserRole.SUPERADMIN);
superuser.setInstId(null); // will be set when Queue receives Institute information
appUserService.saveUser(superuser);
System.out.println("************** superuser creation SUCCESSFUL *********************");
}
}
}
#Component
public class InstituteQueueListener {
#RabbitListener(queues = "institute-queue")
public void updateSuperAdminInstituteId(InstituteEntity institute) {
System.out.println("************** RabbitListener invoked *********************");
Long headInstituteId = institute.getInstId();
Optional<AppUserEntity> user = appUserService.getUserByUserName("superuser");
if(user.isPresent()) {
System.out.println("************* superuser is present *****************");
AppUserEntity superuser = user.get();
superuser.setInstId(headInstituteId);
System.out.println("************* Going to save inst Id = "+headInstituteId);
appUserService.saveUser(superuser);
}
System.out.println("************** superuser is NOT present (inside Q listener)*********************");
}
}
Order of print statements ....
(the queue already has event before running my application)
System.out.println("************** createAdmin invoked *********************");
System.out.println("************** RabbitListener invoked *********************");
System.out.println("************** superuser is NOT present (inside Q listener) *********************");
System.out.println("************** superuser creation SUCCESSFUL *********************");
When you start your application, any CommandLineRunners are called on the main thread (the thread on which you called SpringApplication.run). This happens once the application context has been refreshed and all of its beans have been initialized.
#RabbitListener-annotated methods are called by a message listener container once the container has been started and as messages become available. The container is started as part of the application context being refreshed and, therefore, before your command line runner is called. The container uses a separate pool of threads to call its listeners.
This means that your listener method may be called before, at the same time, or after your command line runner, depending on whether there is a message (event) on the queue.
I'm trying to generate and handle an event when my MDB receives a message. Here is what I'm doing:
public class MDBBooks implements MessageListener {
#Inject
private Event<Update> messageReceived;
public MDBLibri() {
}
#Override
public void onMessage(Message message) {
System.out.println("Message received");
try {
Update u = message.getBody(Update.class);
messageReceived.fire(u);
if(u != null){
... stuff
}
} catch (JMSException ex) {
System.out.println("JMSException: " + ex.getMessage());
}
}
public void eventHandler(#Observes Update up) {
System.out.println("There was an update");
}
}
But it just does not work, the string "There was an update" it's not printed in the glassfish console. I can't really tell what's the problem, my textbook does it the same way pretty much. I'm assuming the event fires fine, but the event handler isn't notified.
You are correct that the observer method does not get notified. In fact, CDI doesn't even know it exists. The reason is that in CDI, message-driven beans are non-contextual objects. To simplify, they are not considered CDI beans, but you can still inject into them and intercept them.
Now, for CDI to recognize an observer method, you have to place it in a managed bean or a session bean. Quoting the spec:
An observer method is a non-abstract method of a managed bean class or session bean class (or of an extension, as defined in Container lifecycle events).
So a solution for you would be to place your observer method in another class, which is either a managed bean or a session bean.
I'd like to refresh my application context when system receives JMS message. In order to do it, I set up Spring Integration jms:message-driven-channel-adapter which forwards message to service activator implementing ApplicationContextAware. This activator (ConfigurationReloader class) invokes ConfigurableApplicationContext#refresh() method.
Below is sample code snippet:
<jms:message-driven-channel-adapter id="jmsDriverConfigurationAdapter"
destination="configurationApplyQueue" channel="jmsConfigurationInboundChannel" />
<channel id="jmsConfigurationInboundChannel"/>
<service-activator input-channel="jmsConfigurationInboundChannel" ref="configurationReloader" method="refresh"/>
And my activator:
public final class ConfigurationReloader implements ApplicationContextAware {
private ConfigurableApplicationContext applicationContext;
public void refresh() {
this.applicationContext.refresh();
}
#Override
public void setApplicationContext(
final ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof ConfigurableApplicationContext) {
this.applicationContext =
(ConfigurableApplicationContext) applicationContext;
}
}
}
In case of delivering such message, context start shutdown operation but stuck on DefaultMessageListenerContainer bean shutdown:
2011-11-14 15:42:52,980 [org.springframework.jms.listener.DefaultMessageLis tenerContainer#0-1] DEBUG org.springframework.jms.listener.DefaultMessageLis tenerContainer - Shutting down JMS listener container
2011-11-14 15:42:52,980 [org.springframework.jms.listener.DefaultMessageLis tenerContainer#0-1] DEBUG org.springframework.jms.listener.DefaultMessageLis tenerContainer - Waiting for shutdown of message listener invokers
2011-11-14 15:42:55,104 [org.springframework.jms.listener.DefaultMessageLis tenerContainer#0-1] DEBUG org.springframework.jms.listener.DefaultMessageLis tenerContainer - Still waiting for shutdown of 1 message listener invokers
Invoking this operation over JMS is crucial for me since new configuration parameters are delivered along with message.
It is standard Spring MVC application with DispatcherServlet on the front based on the latest SpringCore and Spring Integration. Also I am sure that it's JMS related issue, because invoking ConfigurationLoader through controller works fine.
As I've debugged, it stucks after DefaultMessageListenerContainer#538 line invocation (wait() method on lifecycleMonitor):
/**
* Destroy the registered JMS Sessions and associated MessageConsumers.
*/
protected void doShutdown() throws JMSException {
logger.debug("Waiting for shutdown of message listener invokers");
try {
synchronized (this.lifecycleMonitor) {
while (this.activeInvokerCount > 0) {
if (logger.isDebugEnabled()) {
logger.debug("Still waiting for shutdown of " + this.activeInvokerCount +
" message listener invokers");
}
this.lifecycleMonitor.wait(); // <--- line 538
}
}
}
catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
}
}
...there is nobody to call notify / notifyAll on monitor so maybe it's some kind of bug?
Thank you for any hints!
Can you please explain why do you need such sophisticated architecture? Reloading application context when JMS message is received? Sounds crazy (or maybe ingenious?)
Nevertheless, I am not 100% sure but the information you provided is pretty clear: you are trying to shutdown an application context while consuming JMS message. But since the consumer is Spring-managed, context cannot be destroyed because it waits for all beans to finish - including yours ConfigurationReloader required by Spring Integration message consumer. And ConfigurationReloader cannot finish because it waits for context to be destroyed (refresh() is blocking).
Simply put - you have introduced a cyclic dependency and a deadlock.
The solution is simple - delay the context refresh so that it happens after the JMS message consumption. The easiest way would be:
public void refresh() {
Thread destroyThread = new Thread() {
#Override
public void run() {
this.applicationContext.refresh();
}
};
destroyThread.start();
}
Not pretty but I'm almost sure this will work.
I'm trying to unit test the custom events that I've created in Spring and am running into an interesting problem. If I create a StaticApplicationContext and manually register and wire the beans I can trigger events and see the program flow through the publisher (implements ApplicationEventPublisherAware) through to the listener (implements ApplicationListener<?>).
Yet when I try to create a JUnit test to create the context using the SpringJunit4ClassRunner and #ContextConfiguration everything works well except that the ApplicationEvents are not showing up in the listener (I have confirmed that they are getting published).
Is there some other way to create the context so that ApplicationEvents will work correctly? I haven't found much on the web about unit testing the Spring events framework.
The events will not fire because your test classes are not registered and resolved from the spring application context, which is the event publisher.
I've implemented a workaround for this where the event is handled in another class that is registered with Spring as a bean and resolved as part of the test. It isn't pretty, but after wasting the best part of a day trying to find a better solution I am happy with this for now.
My use case was firing an event when a message is received within a RabbitMQ consumer. It is made up of the following:
The wrapper class
Note the Init() function that is called from the test to pass in the callback function after resolving from the container within the test
public class TestEventListenerWrapper {
CountDownLatch countDownLatch;
TestEventWrapperCallbackFunction testEventWrapperCallbackFunction;
public TestEventListenerWrapper(){
}
public void Init(CountDownLatch countDownLatch, TestEventWrapperCallbackFunction testEventWrapperCallbackFunction){
this.countDownLatch = countDownLatch;
this.testEventWrapperCallbackFunction = testEventWrapperCallbackFunction;
}
#EventListener
public void onApplicationEvent(MyEventType1 event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
#EventListener
public void onApplicationEvent(MyEventType2 event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
#EventListener
public void onApplicationEvent(OnQueueMessageReceived event) {
testEventWrapperCallbackFunction.CallbackOnEventFired(event);
countDownLatch.countDown();
}
}
The callback interface
public interface TestEventWrapperCallbackFunction {
void CallbackOnEventFired(ApplicationEvent event);
}
A test configuration class to define the bean which is referenced in the unit test. Before this is useful, it will need to be resolved from the applicationContext and initialsed (see next step)
#Configuration
public class TestContextConfiguration {
#Lazy
#Bean(name="testEventListenerWrapper")
public TestEventListenerWrapper testEventListenerWrapper(){
return new TestEventListenerWrapper();
}
}
Finally, the unit test itself that resolves the bean from the applicationContext and calls the Init() function to pass assertion criteria (this assumes you have registered the bean as a singleton - the default for the Spring applicationContext). The callback function is defined here and also passed to Init().
#ContextConfiguration(classes= {TestContextConfiguration.class,
//..., - other config classes
//..., - other config classes
})
public class QueueListenerUnitTests
extends AbstractTestNGSpringContextTests {
private MessageProcessorManager mockedMessageProcessorManager;
private ChannelAwareMessageListener queueListener;
private OnQueueMessageReceived currentEvent;
#BeforeTest
public void Startup() throws Exception {
this.springTestContextPrepareTestInstance();
queueListener = new QueueListenerImpl(mockedMessageProcessorManager);
((QueueListenerImpl) queueListener).setApplicationEventPublisher(this.applicationContext);
currentEvent = null;
}
#Test
public void HandleMessageReceived_QueueMessageReceivedEventFires_WhenValidMessageIsReceived() throws Exception {
//Arrange
//Other arrange logic
Channel mockedRabbitmqChannel = CreateMockRabbitmqChannel();
CountDownLatch countDownLatch = new CountDownLatch(1);
TestEventWrapperCallbackFunction testEventWrapperCallbackFunction = (ev) -> CallbackOnEventFired(ev);
TestEventListenerWrapper testEventListenerWrapper = (TestEventListenerWrapper)applicationContext.getBean("testEventWrapperOnQueueMessageReceived");
testEventListenerWrapper.Init(countDownLatch, testEventWrapperCallbackFunction);
//Act
queueListener.onMessage(message, mockedRabbitmqChannel);
long awaitTimeoutInMs = 1000;
countDownLatch.await(awaitTimeoutInMs, TimeUnit.MILLISECONDS);
//Assert - assertion goes here
}
//The callback function that passes the event back here so it can be made available to the tests for assertion
private void CallbackOnEventFired(ApplicationEvent event){
currentEvent = (OnQueueMessageReceived)event;
}
}
EDIT 1: The sample code has been updated with CountDownLatch
EDIT 2: Assertions didn't fail tests so the above was updated with a different approach**
I just run my app as SpringBootTest, application events working fine:
#TestComponent
public class EventTestListener {
#EventListener
public void handle(MyCustomEvent event) {
// nothing to do, just spy the method...
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyEventTest {
#SpyBean
private EventTestListener testEventListener;
#Test
public void testMyEventFires() {
// do something that fires the event..
verify(testEventListener).handle(any(MyCustomEvent.class));
}
}
use the #Captor / ArgumentCaptor to verify the content of your event.
You can create a context manually.
For example: I had needed to check if my ApplicationListener<ContextClosedEvent> closed Cassandra connections:
#Test
public void testSpringShutdownHookForCassandra(){
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CassandraConfig.class);
CassandraConnectionManager connectionManager = ctx.getBean(CassandraConnectionManager.class);
Session session = connectionManager.openSession(testKeySpaceName);
Assert.assertFalse( session.isClosed() );
ctx.close();
Assert.assertTrue( session.isClosed() );
}
package com.idol;
public class Auditorium {
Auditorium(){
}
public void turnOnLights() {
System.out.println("Lights are turned on");
}
public void turnOffLights(){
System.out.println("Lights are turned off");
}
}
For xml context I have:
<bean id="Auditorium" class="com.idol.Auditorium" init-method="turnOnLights" destroy-method="turnOffLights"/>
Testing:
ApplicationContext auditorium =
new ClassPathXmlApplicationContext("ApplicationContextVer6.xml");
auditorium.getBean("Auditorium");
I get:
Does only print "Lights are turned on" and doesn't print "Lights are turned off". I though that before destroying the bean it should invoke the destroy-method too, what am I missing or not getting? (I have no errors in log, just in case)
Thanks
Try it like this:
final ConfigurableApplicationContext auditorium =
new ClassPathXmlApplicationContext("ApplicationContextVer6.xml");
auditorium.getBean("Auditorium");
auditorium.close(); // thx Nathan
// auditorium.refresh() will also turn the lights off
// before turning them on again
You cannot observe destroy method working, because beans are available in Spring context all the time. When you close/destroy your application context, then all beans instantiated within it should be destroyed. Take a look at the close() and destroy() methods at the org.springframework.context.support.AbstractApplicationContext class.