Different ways to run custom code before the application starts - spring

Could you describe different ways to run custom code before the application starts for data initialization or something else?
(like ApplicationListener, CommandLineRunner etc.)
What is the difference between all of them? Which cases is better to use each of them in?
I want to know not only one way to do that but an understanding when and what I need to use.
Here is enough old question with too many options to do that: Running code after Spring Boot starts
If it is a wrong place to ask this question, please, point me to the right one.

What options I know:
CommandLineRunner - receive command-line arguments as String
#Slf4j
#Component
public class DemoCommandLineRunner implements CommandLineRunner {
#Override
public void run(String... args) {
log.info("[CommandLineRunner] Args: " + Arrays.toString(args));
}
}
ApplicationRunner - receive command-line arguments with names
#Slf4j
#Component
public class DemoApplicationRunner implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) {
log.info("[ApplicationRunner] Args: ");
nonOptionArgs(args);
optionArgs(args);
}
private void nonOptionArgs(ApplicationArguments args) {
args.getNonOptionArgs().forEach(log::info);
}
private void optionArgs(ApplicationArguments args) {
args.getOptionNames().stream()
.map(args::getOptionValues)
.map(Objects::toString)
.forEach(log::info);
}
}
ApplicationListener - listener for different events (for each event own class)
#Slf4j
#Component
public class DemoApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
logEvent(event);
}
private void logEvent(ApplicationEvent event) {
log.info("[DemoApplicationListener] Event: " + event);
}
}
#EventListener - listener for different events (several events in one bean)
#Slf4j
#Component
public class DemoEventApplicationListener {
#EventListener
public void handleContextRefreshedEvent(ContextRefreshedEvent event) {
logEvent(event);
}
#EventListener
public void handleApplicationReadyEvent(ApplicationReadyEvent event) {
logEvent(event);
}
private void logEvent(ApplicationEvent event) {
log.info("[DemoEventApplicationListener] Event: " + event);
}
}
SmartLifecycle - configure bean lifecycle
#Slf4j
#Component
public class DemoSmartLifecycle implements SmartLifecycle {
private boolean isRunning;
#Override
public void start() {
isRunning = true;
log.info("[DemoSmartLifecycle]: Start");
}
#Override
public void stop() {
isRunning = false;
log.info("[DemoSmartLifecycle]: Stop");
}
#Override
public boolean isRunning() {
return isRunning;
}
}
SmartInitializingSingleton - triggered at the end of the singleton pre-instantiation phase
#Slf4j
#Component
public class DemoSmartInitializingSingleton implements SmartInitializingSingleton {
#Override
public void afterSingletonsInstantiated() {
log.info("[SmartInitializingSingleton] afterSingletonsInstantiated");
}
}
Github repo: https://github.com/venkaDaria/demo-bootstrap-spring

If you need to run some code "once the SpringApplication has started" you should use ApplicationRunner or CommandLineRunner - they work the same way.
ApplicationListener, or #EventListener with ApplicationReadyEvent do the same as well.
See my example.
The option you choose is up to you.

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.

Log ApplicationEventPublisher.publishEvent() calls

I've got an Spring Boot 2.2 Application which publishes and consumes spring application events in different packages. Now I want to log every time an event has been published by ApplicationEventPublisher.publishEvent().
One solution could be to write my own event publisher like:
public class LoggableApplicationEventPublisher implements ApplicationEventPublisher {
private final ApplicationEventPublisher eventPublisher;
private final Logger logger;
public ApplicationEventLogger(ApplicationEventPublisher eventPublisher, Logger logger) {
this.eventPublisher = eventPublisher;
this.logger = logger;
}
#Override
public void publishEvent(ApplicationEvent event) {
eventPublisher.publishEvent(event);
logger.info("--> Emitting {}", event);
}
}
Another solution could be to use aspect oriented programming and write an Aspect which is triggered everytime publishEvent() has been triggered:
#Aspect
#Component
public class EventPublishAspect {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#Pointcut("execution(* org.springframework.context.ApplicationEventPublisher.*(..))")
public void logPublishEvent() {
}
#After("logPublishEvent()")
public void log(JoinPoint point) {
Object[] lArgs = point.getArgs();
LOG.info("Triggered", lArgs[0]);
}
}
I've set up all correctly (dependencies aswell) and this example is working for other pointcuts (like for a call of specific method of my services).
However, this aspect is not working with the declared pointcut for the ApplicationEventPublisher-Interface. Do you know why not? It seems like spring boot injects AbstractApplicationContext on runtime, which is actually implementing this interface.
Solution that does not require aspects (and has faster startup time?)
#Primary
#Bean
DelegatingApplicationEventPublisher applicationEventPublisher(ApplicationContext applicationContext) {
new DelegatingApplicationEventPublisher(applicationContext)
}
#Slf4j
#RequiredArgsConstructor
public class DelegatingApplicationEventPublisher implements ApplicationEventPublisher {
private final ApplicationContext context;
#Override
public void publishEvent(ApplicationEvent event) {
logEvent(event);
context.publishEvent(event);
}
#Override
public void publishEvent(Object event) {
logEvent(event);
context.publishEvent(event);
}
private void logEvent(Object event) {
if (event instanceof PayloadApplicationEvent payloadApplicationEvent) {
log.debug(markers("eventName", payloadApplicationEvent.getPayload().getClass(), "event", payloadApplicationEvent.getPayload()), "publishing...");
} else {
log.debug(markers("eventName", event.getClass(), "event", event), "publishing ...");
}
}
}

How to run Scheduled task after specific CommandLineRunner?

I do some initializations in a CommandLineRunner's run function and I want my Scheduled task to begin schedule after the initialization, how can I achive this?
For example, I have the CommandLineRunner:
#Component
public class MyCommandLineRunner implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
// Initializations
// ...
}
}
And the task scheduler:
public class SchedClass {
#Scheduled(fixedRate = ONE_SECOND)
public void sched() {
}
}
What can I do to make sched() runs after run() runs?
class MySchedulingConfigurer implements SchedulingConfigurer {
private ScheduledTaskRegistrar taskRegistrar;
private IntervalTask task;
public MySchedulingConfigurer(IntervalTask task) {
this.task = task;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
}
public void resume() {
this.taskRegistrar.scheduleFixedRateTask(task);
}
}
#Configuration
class SchedulerConfiguration {
#Autowired
private MySchedulingConfigurer schedulingConfigurer;
#Bean
public MySchedulingConfigurer mySchedulingConfigurer() {
IntervalTask task = new IntervalTask(new MyTask(), 5000L);
return new MySchedulingConfigurer(task);
}
public static class MyTask implements Runnable {
#Override
public void run() {
System.out.println("===> task executed...");
}
}
#EventListener
public void startScheduler(ApplicationReadyEvent event){
schedulingConfigurer.resume();
}
}
#Component
class CacheLoadingRunner implements CommandLineRunner {
#Autowired
private MySchedulingConfigurer schedulingConfigurer;
#Override
public void run(String... args) throws Exception {
schedulingConfigurer.resume();
}
}
1) Add #EnableScheduling
#EnableScheduling
#SpringBootApplication
public class MyCommandLineRunner implements CommandLineRunner {
...
}
2) It makes no sense to denote Spring Boot application with #Component
UPDATED
It could be achieved manually. Provide a TaskScheduler
#Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
return threadPoolTaskScheduler;
}
Inject it into MyCommandLineRunner along with SchedClass and run
#Component
public class MyCommandLineRunner implements CommandLineRunner {
#Autowired
private ThreadPoolTaskScheduler taskScheduler;
#Autowired
private SchedClass schedBean;
#Override
public void run(String... args) throws Exception {
// ...
taskScheduler.scheduleWithFixedDelay(() -> schedBean.sched(), ONE_SECOND);
// ...
}
}

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;
}
}

How to go about Spring autowiring?

public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Override
public void init(ServletConfig arg0) throws ServletException {
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
LogProcessorService logProcessorService=new LogProcessorServiceImpl();
logProcessorService.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
}
This is ugly and it doesn't work, anyway. The LogProcessorServiceImpl has properties with #Autowired annotation. These properties are not autowired when this code runs. This may be expected.
The real question is: how to make this run() method work. It seems to me that Spring wants the logProcessorService to be autowired to have properties within LogProcessorServiceImpl autowired, as well.
=== SCENARIO 1 ==============================================================
public void run() {
final LogProcessorService logProcessorService=null;
WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory().autowireBean(logProcessorService);
logProcessorService.processPageRequestsLogs();
}
Result: compile time error: Cannot refer to a non-final variable arg0 inside an inner class defined in a different method
=== SCENARIO 2 ==============================================================
#Autowired
LogProcessorService logProcessorService;
public void run() {
logProcessorService.processPageRequestsLogs();
}
Result: run time error: logProcessorService is null;
==== SOLUTION (from Boris) ======================================================
public class ProcessSchedulerServlet implements javax.servlet.Servlet {
Timer timer=new Timer();
#Autowired
LogProcessorService logProcessorService;
#Override
public void init(ServletConfig arg0) throws ServletException {
final AutowireCapableBeanFactory autowireCapableBeanFactory=WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()).getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(this);
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
logProcessorService.processPageRequestsLogs();
}
}, 60*1000, 120*1000);
}
Why bother with servlets and Timer class if Spring has a built in scheduling support:
#Service
public class LogProcessorService {
#Scheduled(fixedRate=120*1000, initialDelay=60*1000)
public void processPageRequestsLogs() {
//...
}
}
That's it! No timers, runnables and servlets. Note: initialDelay was introduced in Spring 3.2 M1 (see SPR-7022).

Resources