How to trigger listener after 1 particular bean initialized - spring

Does anybody know if it is possible to create Spring Boot listener that would be called once only 1 particular bean has been initialized?
I only know how to create listener that is triggered once all beans have been initialized:
#EventListener(ContextRefreshedEvent.class)
public void myListener(ContextRefreshedEvent event) {...}
But that listener will be triggered for every single bean in the app instead of 1 particular bean I am looking for.
Any ideas?

Here's how this could be done.
First make your bean publish an event when it's initialized by implementing InitializingBean or having a #PostConstruct method:
public class SomeBeanInitializedEvent extends ApplicationEvent {
...
public SomeBeanInitializedEvent(Object source) {
super(source);
}
}
#RequiredArgsConstructor
public class SomeBean implements InitializingBean {
private final ApplicationEventPublisher applicationEventPublisher;
#Override
public void afterPropertiesSet() {
applicationEventPublisher.publishEvent(new SomeBeanInitializedEvent(this));
}
}
Or as a #Bean method use the standard ApplicationEventPublisher available in the context:
#Bean
public SomeBean someBean(ApplicationEventPublisher publisher) {
SomeBean someBean = ...
publisher.publishEvent(new SomeBeanInitializedEvent(someBean));
return someBean;
}
Then create an event listener for your event:
private class SomeBeanInitializedEventApplicationListener implements ApplicationListener<SomeBeanInitializedEvent> {
#Override
public void onApplicationEvent(SomeBeanInitializedEvent event) {
log.info("Got SomeBeanInitializedEvent: {}", event);
}
}
Then register this application event listener via spring.factories or the setter method on SpringApplication/SpringApplicationBuilder:
#SpringBootApplication
public class SomeApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SomeApplication.class)
.listeners(new SomeBeanInitializedEventApplicationListener())
.run(args);
}
...
You cannot use an #EventListener annotated method in this case because it'll be registered as a listener too late, after an event from your bean has already been fired.

Related

Spring: Publishing event from InitializingBean's afterPropertiesSet method does NOT work

Recently I have found that when I publish an event from org.springframework.beans.factory.InitializingBean.afterPropertiesSet(), then it is unable to publish that event!
However this very same event trigger gets invoked if I invoke it from #Controller or any other class class (the event invocation mechanism remains same for both places).
I have put a print statement after publishing event in InitBean ('Trigger done') and that is successfully printed too.
If you have any idea about this behaviour then please let me know.
Thanks very much
//Sample code for InitializingBean:
#Component
public class InitBean implements InitializingBean {
private final ApplicationEventPublisher publisher;
public InitBean(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
#Override
public void afterPropertiesSet() throws Exception {
this.publisher.publishEvent(new TriggerEvent());
System.out.println("Trigger done");
}
}
// Sample code for trigger event:
public class TriggerEvent extends ApplicationEvent {
public TriggerEvent() {
super("source");
}
}
// Sample code for listener:
#Component
public class TriggerListener {
#EventListener(TriggerEvent.class)
public void trigger(TriggerEvent triggerEvent) {
System.out.println("Trigger event has come");
}
}
Without testing, I think the problem is that afterPropertiesSet is just too early in the Spring Bean live cycle.
Try firing the event a little later.
Rather in a #PostConstruct, an init-method, or when the application context refresh event was catched.
#PostConstruct:
#Component
public class InitBean {
...
#PostConstruct
public void fire() throws Exception {
this.publisher.publishEvent(new TriggerEvent());
System.out.println("Trigger done");
}
}
init-method:
#Configuration
public class AppConfig {
#Bean(initMethod="fire")
public InitBean initBean (ApplicationEventPublisher publisher) {
return new InitBean (publisher);
}
}
public class InitBean {
...
#PostConstruct
public void fire() throws Exception {
this.publisher.publishEvent(new TriggerEvent());
System.out.println("Trigger done");
}
}
context refresh way
#Component
public class InitBean {
...
#EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
fire();
}
public void fire() throws Exception {
this.publisher.publishEvent(new TriggerEvent());
System.out.println("Trigger done");
}
}
I don't know if the first two approaches solve the suspected problem, but the last one should work.

Unit testing of Spring ApplicationEventPublisher fails to consume the events

I want to integration test a Spring Boot 1.5.4 service that uses an #EventListener. My problem is: when the test is run, the events are correctly published, but they are not consumed.
My ultimate purpose is to use a #TransactionEventListener, but for simplicity I start with an #EventListener.
Here is my service class:
#Service
public class MyService {
private static final Logger logger = // ...
private final ApplicationEventPublisher eventPublisher;
#Autowired
public MyService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
#Transactional
public void publish() {
logger.warn("Publishing!");
eventPublisher.publishEvent(new MyEvent());
}
// #TransactionalEventListener
#EventListener
public void consume(MyEvent event) {
logger.warn("Consuming!"); // this is never executed in the test
}
public static class MyEvent {
}
}
Here is my JUnit test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#SpringBootConfiguration
public class MyServiceIT {
#Autowired
MyService myService;
#Test
public void doSomething() {
myService.publish();
}
static class TestConfig {
#Bean
public MyService myService() {
return new MyService(eventPublisher());
}
#Bean
public ApplicationEventPublisher eventPublisher() {
ApplicationEventPublisher ctx = new GenericApplicationContext();
((AbstractApplicationContext)ctx).refresh();
return ctx;
}
}
}
Note: the call to refresh() prevents an IllegalStateException with message "ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context" from occurring.
Does anyone have a clue? Many thanks in advance.
For the record, the solution was: keep the event consumer method in another bean than the event producer method. That is, extract method consume(MyEvent) from class MyService into a new #Service class MyConsumer.

spring boot how to unregister guava eventbus listener?

there's a spring boot application. and I create an event like UserCreateEvent and have a listener UserCreateListener
event:
public class UserCreateEvent {
private Long userId;
}
listener:
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
#Subscribe
public void onUserCreate(UserCreateEvent event) {
Long userId = event.getUserId();
// todo something necessary
}
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
now, I want to unregister the UserCreateListener after spring boot application startup. how can I make Eventbus unregister this event and listener??
Introduce an unregister() method in the UserCreateListener
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
public void unregister() {
this.eventbus.unregister(this)
}
}
Then if you want to unregister get autowired UserCreateListener (or retrieve the bean from application context) and call listenerInstance.unregister()
UPDATE
Create your own component and call unregister there. Guess the listener and eventbus are singletons.
#Component
public class MyUnregisterService {
#Autowired
private Eventbus eventbus;
#Autowired
private UserCreateListener listener;
public void unregister() {
eventbus.unregister(listener)
}
}

Exiting a SpringBoot app - how do I get a reference to SpringApplication?

I realize that to programmatically exit a SpringBoot 4 application I want to call the exit() method of SpringApplication, but how can I get a reference to it?
Of course I have access to it in my main() method, but I ask because if I'm in some class that is loading a resource and fails, I want to terminate the app, but from that class I can't figure out how to access SpringApplication.
Thanks...
A cleaner approach for this use case is to use events & listeners wherein you have to add your listener to SpringApplication class which will listens to an event like in your case a resource load failure and then subsequently act accordingly i.e. exit the application. You can get application context handle by implementing the ApplicationContextAware interface. Details on event & listener can be found here.
MyEvent class :-
public class MyEvent extends ContextRefreshedEvent {
private static final long serialVersionUID = 1L;
#Autowired
public MyEvent(ApplicationContext source) {
super(source);
}
}
MyEvent listener class :-
#Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event instanceof MyEvent){
SpringApplication.exit(event.getApplicationContext(), new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 2;
}
});
}
}
}
Resource loader class:-
#Component
public class MyResourceLoader implements ApplicationContextAware, CommandLineRunner {
private ApplicationContext ctx ;
#Autowired
private ApplicationEventPublisher publisher;
#Override
public void run(String... args) throws Exception {
//inside RUN for resource load failure
publisher.publishEvent(new MyEvent(ctx));
}
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
ctx = applicationContext;
}
}

#Transactional on #PostConstruct method

I want to read text data fixtures (CSV files) at the start on my application and put it in my database.
For that, I have created a PopulationService with an initialization method (#PostConstruct annotation).
I also want them to be executed in a single transaction, and hence I added #Transactional on the same method.
However, the #Transactional seems to be ignored :
The transaction is started / stopped at my low level DAO methods.
Do I need to manage the transaction manually then ?
Quote from legacy (closed) Spring forum:
In the #PostConstruct (as with the afterPropertiesSet from the InitializingBean interface) there is no way to ensure that all the post processing is already done, so (indeed) there can be no Transactions. The only way to ensure that that is working is by using a TransactionTemplate.
So if you would like something in your #PostConstruct to be executed within transaction you have to do something like this:
#Service("something")
public class Something {
#Autowired
#Qualifier("transactionManager")
protected PlatformTransactionManager txManager;
#PostConstruct
private void init(){
TransactionTemplate tmpl = new TransactionTemplate(txManager);
tmpl.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//PUT YOUR CALL TO SERVICE HERE
}
});
}
}
I think #PostConstruct only ensures the preprocessing/injection of your current class is finished. It does not mean that the initialization of the whole application context is finished.
However you can use the spring event system to receive an event when the initialization of the application context is finished:
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
// do startup code ..
}
}
See the documentation section Standard and Custom Events for more details.
As an update, from Spring 4.2 the #EventListener annotation allows a cleaner implementation:
#Service
public class InitService {
#Autowired
MyDAO myDAO;
#EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
event.getApplicationContext().getBean(InitService.class).initialize();
}
#Transactional
public void initialize() {
// use the DAO
}
}
Inject self and call through it the #Transactional method
public class AccountService {
#Autowired
private AccountService self;
#Transactional
public void resetAllAccounts(){
//...
}
#PostConstruct
private void init(){
self.resetAllAccounts();
}
}
For older Spring versions which do not support self-injection, inject BeanFactory and get self as beanFactory.getBean(AccountService.class)
EDIT
It looks like that since this solution has been posted 1.5 years ago developers are still under impression that if a method,
annotated with #Transactional, is called from a #PostContruct-annotated method invoked upon the Bean initialization, it won't be actually executed inside of Spring Transaction, and awkward (obsolete?) solutions get discussed and accepted instead of this very simple and straightforward one and the latter even gets downvoted.
The Doubting Thomases :) are welcome to check out an example Spring Boot application at GitHub which implements the described above solution.
What actually causes, IMHO, the confusion: the call to #Transactional method should be done through a proxied version of a Bean where such method is defined.
When a #Transactional method is called from another Bean, that another Bean usually injects this one and invokes its proxied (e.g. through #Autowired) version of it, and everything is fine.
When a #Transactional method is called from the same Bean directly, through usual Java call, the Spring AOP/Proxy machinery is not involved and the method is not executed inside of Transaction.
When, as in the suggested solution, a #Transactional method is called from the same Bean through self-injected proxy (self field), the situation is basically equivalent to a case 1.
#Platon Serbin's answer didn't work for me. So I kept searching and found the following answer that saved my life. :D
The answer is here No Session Hibernate in #PostConstruct, which I took the liberty to transcribe:
#Service("myService")
#Transactional(readOnly = true)
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
private CacheList cacheList;
#Autowired
public void MyServiceImpl(PlatformTransactionManager transactionManager) {
this.cacheList = (CacheList) new TransactionTemplate(transactionManager).execute(new TransactionCallback(){
#Override
public Object doInTransaction(TransactionStatus transactionStatus) {
CacheList cacheList = new CacheList();
cacheList.reloadCache(MyServiceImpl.this.myDao.getAllFromServer());
return cacheList;
}
});
}
The transaction part of spring might not be initialized completely at #PostConstruct.
Use a listener to the ContextRefreshedEvent event to ensure, that transactions are available:
#Component
public class YourService
implements ApplicationListener<ContextRefreshedEvent> // <= ensure correct timing!
{
private final YourRepo repo;
public YourService (YourRepo repo) {this.repo = repo;}
#Transactional // <= ensure transaction!
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
repo.doSomethingWithinTransaction();
}
}
Using transactionOperations.execute() in #PostConstruct or in #NoTransaction method both works
#Service
public class ConfigurationService implements ApplicationContextAware {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationService.class);
private ConfigDAO dao;
private TransactionOperations transactionOperations;
#Autowired
public void setTransactionOperations(TransactionOperations transactionOperations) {
this.transactionOperations = transactionOperations;
}
#Autowired
public void setConfigurationDAO(ConfigDAO dao) {
this.dao = dao;
}
#PostConstruct
public void postConstruct() {
try { transactionOperations.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
ResultSet<Config> configs = dao.queryAll();
}
});
}
catch (Exception ex)
{
LOG.trace(ex.getMessage(), ex);
}
}
#NoTransaction
public void saveConfiguration(final Configuration configuration, final boolean applicationSpecific) {
String name = configuration.getName();
Configuration original = transactionOperations.execute((TransactionCallback<Configuration>) status ->
getConfiguration(configuration.getName(), applicationSpecific, null));
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}

Resources