How to implement ApplicationRunner before springboot application starts? - java

By reading Spring-boot docs I have understood that I can create a class implementing ApplicationRunner or CommandLineRunner in order to execute code before application starts.
From docs:
An ApplicationReadyEvent is sent after any application and command-line runners have been called. It indicates that the application is ready to service requests.
However, I have following class:
#Component
public class MyClass implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
Thread.sleep(10000);
}
}
And instead of waiting those 10 seconds to start, it says application started in 3seconds:
2018-11-19 08:51:21.906 INFO 24872 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-11-19 08:51:21.909 INFO 24872 --- [ main] com.mycompany.Application : Started Application in 3.565 seconds (JVM running for 4.016)
I assume my class is running in another thread. But I have the feeling that this does not guarantee that my code has finished executing before any possible incoming request.
Is this the correct approach? Am I missing something?

Related

How to avoid default Quartz Scheduler in Spring Boot application?

I'm setting up a Quartz Scheduler in Spring Boot. I want the scheduled Jobs to be able to inject Beans from the Application Context. For that I created my own Quartz Job Factory:
public final class MyQuartzJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext context) throws BeansException {
beanFactory = context.getAutowireCapableBeanFactory();
}
#Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
For keeping a reference to the Scheduler, I have a SchedulerManager Bean that initializes the Scheduler:
public void initScheduler() {
try (InputStream propsInputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("my-quartz.properties")) {
log.info("Creating Quartz scheduler...");
Properties myQuartzProperties = new Properties();
myQuartzProperties.load(propsInputStream);
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setQuartzProperties(myQuartzProperties);
schedulerFactory.afterPropertiesSet();
schedulerFactory.setJobFactory(quartzJobFactory);
this.scheduler = schedulerFactory.getScheduler();
this.schedulingConfigMap = new HashMap<>();
log.info("Quartz scheduler created.");
} catch (Exception e) {
log.error("Unable to create Quartz scheduler", e);
throw new RuntimeException("Scheduler extension initialization error", e);
}
}
I do the initialization of the Scheduler once the context has been initialized:
public class MyLifecycleListener implements ServletContextListener {
private final SchedulerManager schedulerManager;
#Override
public void contextInitialized(ServletContextEvent sce) {
schedulerManager.initScheduler();
}
}
The quartz configuration file is as follows:
org.quartz.scheduler.instanceName=MyQuartzScheduler
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=12
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
org.quartz.jobStore.misfireThreshold=60000
When starting the application I see my Quartz Scheduler is initialized, but the problem is that I see a second Quartz Scheduler initialized (maybe a Spring default one?).
Mine: 'MyQuartzScheduler' with 12 threads
2022-08-23 14:13:03.239 INFO 24788 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2022-08-23 14:13:03.240 INFO 24788 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2022-08-23 14:13:03.241 INFO 24788 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'MyQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 12 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2022-08-23 14:13:03.241 INFO 24788 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'MyQuartzScheduler' initialized from an externally provided properties instance.
Some milliseconds later, another one called 'quartzScheduler' with 10 threads.
2022-08-23 14:13:03.713 INFO 24788 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2022-08-23 14:13:03.713 INFO 24788 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2022-08-23 14:13:03.713 INFO 24788 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2022-08-23 14:13:03.713 INFO 24788 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
How can I disable this second one?
Sample project can be found here: https://github.com/dajoropo/spring-quartz-demo/tree/stackoverflow_question_73459083
The easiest way is to use the Spring configured scheduler instead of doing it yourself. Move the quartz.properties to the spring.quartz namespace in the application.properties.
spring.quartz.scheduler-name=MyQuartzScheduler
spring.quartz.properties.org.quartz.threadPool.threadCount=12
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
To configure your own JobFactory write a SchedulerFactoryBeanCustomizer to configure it on the SchedulerFactoryBean.
#Component
class MySchedulerFactoryBeanCustomizer implements SchedulerFactoryBeanCustomizer {
void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setJobFactory(new MyQuartzJobFactory());
}
}
Now ditch the quartz.properties, MyLifecycleListener and the SchedulerManager and let Spring handle all that for you.

Micronaut context configuration for test

I'm trying to write a basic controller test in a micronaut (3.2.7) application. When I run it, it fails to start as it wants to create DB related beans too. micronaut-hibernate-jpa, flyway, etc. are in the pom.xml.
Can I configure the context somehow so it doesn't pick up hikaripool,flyway, and jpa realted beans?
11:46:23.820 [main] INFO i.m.context.env.DefaultEnvironment - Established active environments: [test]
11:46:24.112 [main] WARN i.m.c.h.j.JpaConfiguration$EntityScanConfiguration - Runtime classpath scanning is no longer supported. Use #Introspected to declare the packages you want to index at build time. Example #Introspected(packages="foo.bar", includedAnnotations=Entity.class)
11:46:24.133 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
11:46:25.197 [main] ERROR com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Exception during pool initialization.
org.postgresql.util.PSQLException: FATAL: password authentication failed for user "postgres"
The code:
class HelloTest {
private static EmbeddedServer server;
private static HttpClient client;
#BeforeAll
public static void setupServer() {
server = ApplicationContext.run(EmbeddedServer.class);
client = server
.getApplicationContext()
.createBean(HttpClient.class, server.getURL());
}
#AfterAll
public static void stopServer() {
if (server != null) {
server.stop();
}
if (client != null) {
client.stop();
}
}
#Test
void testHelloWorldResponse() {
...
}
}
I tried to exclude configurations like this, but with no luck:
server = ApplicationContext.builder("test")
.exclude("io.micronaut.configuration.hibernate.jpa","io.micronaut.configuration.jdbc.hikari")
.run(EmbeddedServer.class);
Note: If I remove everything from application.yml then the test works. It looks like that in tests the default properties are resolved which turns on jpa,metrics, etc. So I guess the test needs to ignore the default settings too somehow.
You can override all of your (default) application.yml with (test-)environment specific property files: https://docs.micronaut.io/latest/guide/index.html#_included_propertysource_loaders
So you can just provide a dedicated application-mycustomtest.yml as part of your test resources, in which you override all default settings.
Then you can specify as part of the test, which environments shall be active:
#MicronautTest(environments={"mycustomtest"})
Asked the micronaut team on gitter and currenlty the only option is not having a default configuration and having multiple configuration files for controller, repo and e2e testing.

Spring boot how to use #PostConstruct correctly

Spring boot 2.5.4 I used #PostConstruct for the very first time in my service class. As following:-
#Slf4j
#Service
#AllArgsConstructor
public class FileMonitorService {
private final AppProperties appProperties;
private final WatchService watchService;
private final RestTemplate restTemplate;
#PostConstruct
#Async
public void startMonitoring() {
FileUtils.setAppProperties(appProperties);
FileUtils.setRestTemplate(restTemplate);
FileUtils.readFilesForDirectory();
log.info("START_MONITORING");
try {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
log.info("Event kind: {}; File affected: {}", event.kind(), event.context());
if((event.kind() == StandardWatchEventKinds.ENTRY_CREATE ||
event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) &&
event.context().toString().contains(".xml")){
try {
restTemplateRequest(event.context().toString()+" processing");
FileUtils.readXml(Path.of(FileUtils.getFileAbsolutePath(appProperties.getDataIn()),
event.context().toString()));
}catch (Exception e){
log.error("startMonitoring Exception: "+e.getMessage());
}
}
}
key.reset();
}
} catch (InterruptedException e) {
log.warn("startMonitoring: interrupted exception for monitoring service: "+e.getMessage());
}
}
}
This method is called as soon as app launched. That is my requirements to process all file as soon as the app starts. I have controller as following:-
#RestController
#RequestMapping("/xml")
public class FileController {
#Autowired
FileMonitorService fileMonitorService;
#SneakyThrows
#GetMapping("/restart")
public String restartFileMonitoring(){
fileMonitorService.startMonitoring();
return "File monitoring restarted started successfully";
}
}
My app starts on port 8080 and no exception at all. But when I get call this end point localhost:8080/xml/restart
It is not reachable. If I comment out the #PostConstruct then I can call the end point. I am confused how to use this annotation properly. What is wrong in my code?
Update info:-
:: Spring Boot :: (v2.5.4)
2021-09-14 18:23:21.521 INFO 71192 --- [ main] c.f.i.task.BatchProcessorApplication : Starting BatchProcessorApplication using Java 14.0.2 on dev with PID 71192 (/home/dev/Desktop/batch-processor/batch-processor/target/classes started by dev in /home/dev/Desktop/batch-processor/batch-processor)
2021-09-14 18:23:21.523 INFO 71192 --- [ main] c.f.i.task.BatchProcessorApplication : No active profile set, falling back to default profiles: default
2021-09-14 18:23:22.485 INFO 71192 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-09-14 18:23:22.495 INFO 71192 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-09-14 18:23:22.495 INFO 71192 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2021-09-14 18:23:22.564 INFO 71192 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-09-14 18:23:22.564 INFO 71192 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 988 ms
File to monitor: /home/dev/Desktop/batch-processor/batch-processor/data/in
2021-09-14 18:23:22.647 INFO 71192 --- [ main] c.f.i.task.config.FileMonitorConfig : MONITORING_DIR: /home/dev/Desktop/batch-processor/batch-processor/data/in/
2021-09-14 18:23:22.667 INFO 71192 --- [ main] c.f.i.task.service.FileMonitorService : START_MONITORING
That is the log when I run the app. After debugging I found that while ((key = watchService.take()) != null) { call never returns until I copy some XML file as this app process xml files. Then I copy any xml file in the monitoring dir. I was expecting that #Async it will run in back ground thread in async mode. How to monitory this dir in background thread? So the caller of this method won't be blocked.
PostContstruct semantics
The PostConstruct annotation is part of JSR 330 (Dependency Injection) and is not a Spring custom annotation.
The annotation specification dictates that the annotated method MUST run before the service being injected into context or translated into a service.
Spring supports the PostConstruct lifecycle hook allowing to perform extra post-initialization actions once a bean has been initialized, i.e., it had all its dependencies injected.
Async semantics
The Async annotation on the other hand is a Spring specific annotation allowing to mark a method or a type as being a candidate for asynchronous execution.
Alternative
In a case where you are interested into starting a background process as long as you application starts, you should better use the application lifecycle events and more specifically the ApplicationReadyEvent to spin your monitoring activity:
#Slf4j
#Service
#AllArgsConstructor
public class FileMonitorService {
private final AppProperties appProperties;
private final WatchService watchService;
private final RestTemplate restTemplate;
#EventListener(ApplicationReadyEvent.class)
#Async
public void startMonitoring() {
// ...
}
}
And don't forget to add the #EnableAsync annotation on your Spring Boot configuration type to activate the asynchronous processing feature.
For your case, you don't need to use #PostConstruct and that is why its working when removing the #PostConstruct
to simplify, #PostConstruct is considered as a class empty constructor but it make sure all the Beans are loaded before being called

Spring Boot initialization not completing [Application stuck]

While starting the Spring boot, application gets stuck and SpringApplication.run is not returning.
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class AccountServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(AccountServiceApplication.class, args);
System.out.println("----------------------------- I'm done -------------------------");
}
}
I can see that Spring creates all the beans but it never returns, though embedded tomcat is started on 9000, it's not listening
class org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory's object [after] tomcatEmbeddedServletContainerFactory
[05/16/19 06:17:44:044 IST] INFO tomcat.TomcatEmbeddedServletContainer: : Tomcat initialized with port(s): 9000 (http)
May 16, 2019 6:17:44 AM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-9000"]
May 16, 2019 6:17:44 AM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Trying 127.0.0.1...
Connection failed: Connection refused
Trying ::1...
telnet: Unable to connect to remote host: Connection refused
Application is stuck forever post creating JMS connection.
class org.springframework.expression.spel.support.StandardEvaluationContext's object [after] integrationEvaluationContext
class org.springframework.integration.handler.LoggingHandler's object [after] _org.springframework.integration.errorLogger.handler
class org.springframework.integration.config.ConsumerEndpointFactoryBean's object [before]_org.springframework.integration.errorLogger
class org.springframework.integration.config.ConsumerEndpointFactoryBean's object [after] _org.springframework.integration.errorLogger
class org.springframework.integration.config.IdGeneratorConfigurer's object [before]org.springframework.integration.config.IdGeneratorConfigurer#0
class org.springframework.integration.config.IdGeneratorConfigurer's object [after] org.springframework.integration.config.IdGeneratorConfigurer#0
[05/16/19 05:58:02:002 IST] INFO annotation.AnnotationMBeanExporter: : Registering beans for JMX exposure on startup
class org.springframework.expression.spel.support.StandardEvaluationContext's object [after] integrationEvaluationContext
[05/16/19 05:58:02:002 IST] INFO support.DefaultLifecycleProcessor: : Starting beans in phase 0
[05/16/19 05:58:02:002 IST] INFO endpoint.EventDrivenConsumer: : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
[05/16/19 05:58:02:002 IST] INFO channel.PublishSubscribeChannel: : Channel 'application:9000.errorChannel' has 1 subscriber(s).
[05/16/19 05:58:02:002 IST] INFO endpoint.EventDrivenConsumer: : started _org.springframework.integration.errorLogger
[05/16/19 05:58:02:002 IST] INFO support.DefaultLifecycleProcessor: : Starting beans in phase 2147483647
[05/16/19 05:58:02:002 IST] INFO connection.CachingConnectionFactory: : Established shared JMS Connection: ActiveMQConnection {id=ID:SDSD121SFSSDF.local-54305-1557966482134-1:1,clientId=null,started=false}
Any pointer on how can I debug it?
in your application,it may be exist a thread with an infinite loop. your application start up fail. and this thread is not quit. the process still is exist.it looks like start up successfully.
The common practice that might help in such cases is to "catch the lion in the desert / binary search" for the cause the problem.
Try comment out halves of your application till you reach a point where the application starts with no problem. Then you will be able to nail the problematic code/section.
I also got stuck at :
INFO support.DefaultLifecycleProcessor: : Starting beans in phase X
It was because in a #RestController class I made a #GetMapping method return a #Entity, where it should have been a DTO instead.

Scheduler not running in Spring Boot

I have created a Spring Boot application. I have configured my class that contains the scheduler method startService().
Below is my code :
Service Class :
package com.mk.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.mk.envers.model.BossExtChange;
import com.mk.envers.model.BossExtChangeRepository;
#Component
public class EnverseDemoService {
#Autowired
BossExtChangeRepository bossExtChangeRepository;
#Scheduled(fixedRate = 30000)
public void startService() {
System.out.println("Calling startService()");
BossExtChange bossExtChange = bossExtChangeRepository.findById(5256868L);
System.out.println("bossExtChange.getDescription()--->"+bossExtChange.getDescription());
System.out.println("Ending startService()");
}
}
Main Class :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
#SpringBootApplication
#EnableScheduling
#PropertySource("classpath:application.properties")
public class EnverseDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EnverseDemoApplication.class, args);
}
}
I have annotated the class as #Component and also method as #Scheduled(fixedRate = 30000) that will running as a scheduler. But while running the application as Spring Boot the scheduler does not trigger. The console show the below message:
2016-02-03 10:56:47.708 INFO 10136 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2016-02-03 10:56:47.721 INFO 10136 --- [ main] com.mk.envers.EnverseDemoApplication : Started EnverseDemoApplication in 3.231 seconds (JVM running for 3.623)
2016-02-03 10:56:47.721 INFO 10136 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#49e202ad: startup date [Wed Feb 03 10:56:44 IST 2016]; root of context hierarchy
2016-02-03 10:56:47.721 INFO 10136 --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
2016-02-03 10:56:47.736 INFO 10136 --- [ Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
Can anyone please help me out.
May be you can solve this problem by adding the #ComponentScan annotation in the configuration file
#SpringBootApplication
#EnableScheduling
#ComponentScan(basePackages = "com.mk.service")
#PropertySource("classpath:application.properties")
public class EnverseDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EnverseDemoApplication.class, args);
}
}
I was able to solve the issue, I forgot to provide #service level annotation,
I have created the check List for #Scheduler, kindly go through every point one by one, It will help you to solve the issue.
Check for the #EnableScheduling on SpringBoot Main class.
Scheduled method should be annotated with #Scheduled, Follow the #Scheduled Method rules. a method should have the void return type, a method should not accept any parameters.
Make sure the class should be annotated with #Service or #Component Annotation so SpringBoot can make the object of that class.
The package of the scheduler jobs should be under the main Application class's package. e.g com.company is your main application class package then scheduler class package should be com.company.scheduler,
If you are using Cron expression confirm the same e.g #Scheduled( cron = "0 0/2 * * * ?"), This Cron expression will schedule the task for every 2 min.
Feel free to add more points in the comment so it will help to solve the issue.
It must be that you forgot to add #EnableScheduling annotation in your app class.
public static void main(String[] args) {
context = SpringApplication.run(YouApplication.class, args);
}
I was finally able to solve the above issue, I changed the package of my service class EnverseDemoService from package com.mk.service; to com.mk.envers.service;. This is because if the main configuration class EnverseDemoApplication is present in the package com.mk.envers. All the other classes in the boot application should be in the qualifying package. Eg: com.mk.envers.*;
In my case, was the lazy-initialization with value true which was preventing my #Component to be loaded by Spring at startup and #Scheduled method was never running.
Make sure the Spring Boot lazy initialization is disabled.
Please check if in application.properties you have
"spring.main.lazy-initialization=true"
Remove this from application.properties.
Even if you have all the configuration correct, this simple line will enable lazy loading due to which your #Component will initalize during application start.
As Swapnil already mentioned all the check-points to make sure while using cron. One additional thing you should do:
Always verify your cron expression whether it's in right format or not, using below reference site - http://www.freeformatter.com/cron-expression-generator-quartz.html#

Categories

Resources