I have a requirement to add schedulers, which will run on a daily basis, but at the same time, I want to run the scheduler on the application startup. But the problem is schedular is not running immediately after application startup.
You can implement the ApplicationRunner interface and execute your business logic in the run method
#Component
public class TaskRun implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
// do something
}
}
Finally, I solved this by using a listener in the Application.java
#EventListener(ApplicationReadyEvent.class)
public void doSomethingOnceAppIsReady() {
//Calling a schedular method
mySchedular();
}
Related
I'm trying to start my non-web/non-batch Spring boot application properly.
However, if I place the main tasks in a CommandLineRunner it also gets triggered while running the tests. Running the tasks as batch job will work, but my task doesn't follow batch job semantics.
Is extending SpringApplication class and putting the logic in run() method after super() call the standard way?
If you annotate your CommandLineRunner bean with the #Profile annotation, you can tell Spring to only create the bean when running with (or without) certain profiles, for example:
#Component
#Profile("autorun")
public class JobRunner implements CommandLineRunner {
// ...
}
As long as you don't use those profile when testing, it should not be invoked. You can then run the application using the -Dspring.profiles.active=autorun parameter.
You can create a separate Component class and assign a profile
#Component
#Profile("!test")
public class RunApplication implements CommandLineRunner {
#Override
public void run(String... args) throws IOException {
//Your code here
}
}
This class will only be initialized when the spring.profiles.active variable is not equal to test
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/CommandLineRunner.html
I have my standard Spring Boot application working. I have situations where I want to run a "job" which is basically some specific method normally run via a user doing something in their browser but I want to run it from command line.
I'm able to run an arbitrary class with gradlew;
./gradlew -PmainClass=kcentral.backingservices.URLMetaExtractor execute
However when run this way none of the "autowiring" works. What is a better way to execute an arbitrary class (that has a main method) such that it also works with any Autowiring?
EDIT:
I got some advice to use a CommandLineRunner and some args, which work to execute the command via:
./gradlew bootRun -Pargs=--reloadTestData
However, the Autowiring of my Repo is failing. What I have is:
#EnableAutoConfiguration
#EnableMongoAuditing
#EnableMongoRepositories(basePackageClasses=KCItemRepo.class)
#ComponentScan(basePackages = {"kcentral"})
public class ReloadTestData implements CommandLineRunner {
#Autowired
AddItemService addItemService;
#Autowired
KCItemRepo itemRepo;
#Autowired
KCItemRatingRepo itemRatingRepo;
private static final Logger log = LoggerFactory.getLogger(ReloadTestData.class);
public void reloadData(){
log.info("reloadData and called");
if (itemRepo == null){
log.error("Repo not found");
return;
}
long c = itemRepo.count();
log.warn("REMOVING ALL items "+c);
itemRepo.deleteAll();
log.warn("REMOVING ALL ratings");
itemRatingRepo.deleteAll();
}
itemRepo is always null even though I wire the same way in my 'regular' spring boot app without an issue. What do I need to do to have it wire properly?
The fact that you say you want to run a "job" suggests that you might want to use a scheduled task within your application, rather than trying to run it through the command line. e.g. Scheduling tasks in Spring
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
If you want to make a command line application work with Autowiring, you can make a command line application by making your Application class implement the CommandLineRunner interface, e.g. Spring Boot Console App
#SpringBootApplication
public class SpringBootConsoleApplication
implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) {
}
}
And add spring.main.web-application-type=NONE to the properties file.
If you want to stop the application after running you can use SpringApplication.exit(ctx). Don't know about your auto-wiring problem though, maybe try printing out the list of available beans which might give some insight. Example:
#Component
public class DoThenQuit implements CommandLineRunner {
#Autowired
private ApplicationContext ctx;
#Override
public void run(String[] args) {
// do some other stuff before quitting
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);
// then quit the application
SpringApplication.exit(ctx);
}
}
I would like to read data to List or Map from database on startup.
Which is the best way to do it? The Spring Boot version is 5.
Is the below solution is good?
#Component
public class ApplicationStartup
implements ApplicationListener<ApplicationReadyEvent> {
/**
* This event is executed as late as conceivably possible to indicate that
* the application is ready to service requests.
*/
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// here your code ...
return;
}
}
I'd like to storage data on static class, but I have doubt that is the best solution.
I don't quite understand what is your motive for doing so but for doing so you can create a bean using #Component and in that bean create a method with annotation #PostConstruct. you can do whatever you want in this method.
Using the ApplicationRunner interface is the best way to run code once the Spring boot context has loaded.
#Component
public class ApplicationStartup implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
}
}
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-command-line-runner
Is there a clean way to detect when a spring-boot application is stopped and perform some action before? Kind of CommandLineRunner for stopping a service
Thanks in advance
Similar to ApplicationReadyEvent you can use ContextClosedEvent:
#Component
public class ContextClosedEventListener {
#EventListener(ContextClosedEvent.class)
public void onContextClosedEvent(ContextClosedEvent contextClosedEvent) {
System.out.println("ContextClosedEvent occurred at millis: " + contextClosedEvent.getTimestamp());
}
}
I've come up with this solution. If you have better one, feel free to share
#Component
public class PortalServiceLifeCycle implements CommandLineRunner {
static final Logger LOGGER = LoggerFactory.getLogger(PortalServiceLifeCycle.class);
#Override
public void run(String... arg0) throws Exception {
LOGGER.info("###START FROM THE LIFECYCLE###");
}
#PreDestroy
public void onExit() {
LOGGER.info("###STOP FROM THE LIFECYCLE###");
}
}
Don't know if you have resolve this problem perfectly. I meet this issue recently, and have got a solution that a little different.
Firstly, my Spring boot Application is a Tomcat embedded one. (The second method of this issue doesn't depends on the web structure. don't mad, my friend.) In this case, it's naturally to get the idea of catch the stop event by register a listener. I do it like this,
#WebListener
public class HelloListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("HelloListener contextInitialized");
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("HelloListener contextDestroyed");
}
}
and, at the same time, add the annotation #ServletComponentScan on your Application class.
Surely, there are some other ways to register a ServletContextListener, and once you registered it, you can get the stop event in the contextDestroyed function.
BUT, that don't match my issue very much. I must catch the stop event BEFORE the Spring Beans being destroyed. And here comes the second solution.
modify your application main method like the follow:
SpringApplication application = new SpringApplication(DemoApplication.class);
application.addListeners(new MyListener());
application.run(args);
and provide the defination of class MyListener:
class MyListener implements ApplicationListener<ContextClosedEvent>{
#Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
// your code here
}
}
NOTE: the second solution has nothing to do with Tomcat or other web container. The ContextClosedEvent isn't introduced in the Spring document, but I found it in the source, it's very useful i think.
I will be very glad if this can help some one.
It depends what you want to do but one thing you could do is have a bean that implements SmartLifecycle and implement the stop method. Whenever the context is being stopped, you'd get a callback. Note that it does not necessarily means that the process is shutting down. If you want to invoke some code when that happens, I'd register a shutdown hook as Sven wrote in a comment.
I need to be able to have my program exit with an error code so that the scheduler that initiated the program can know that it failed. Currently, I am running my job via SpringApplicationBuilder.
#SpringBootApplication
#EnableBatchProcessing
#Slf4j
public class WeeklyImportApplication extends DefaultBatchConfigurer {
...
public static void main(String[] args) {
handleArguments(args);
new SpringApplicationBuilder(WeeklyImportApplication.class).listeners(new CustomLoggingConfigurationApplicationListener(logConfigurer)).run(args);
finished();
}
#Bean
public Job weeklyImport(JobBuilderFactory jobs, Step determineTableName, Step determineColumnNames, Step readAccessDb) {
return jobs.get("weeklyImport").incrementer(new RunIdIncrementer()).flow(determineTableName).next(determineColumnNames).next(readAccessDb).end().build();
}
#Bean
public Step determineTableName(StepBuilderFactory stepBuilderFactory, ItemReader<String> tableNameReader, TableNameWriter tableNameWriter) {
return stepBuilderFactory.get("determineTableName").<String, String> chunk(100).reader(tableNameReader).writer(tableNameWriter).build();
}
#Bean
public Step determineColumnNames(StepBuilderFactory stepBuilderFactory, ItemReader<String> columnNamesReader, ColumnNamesWriter columnNamesWriter) {
return stepBuilderFactory.get("determineColumnNames").<String, String> chunk(1000).reader(columnNamesReader).writer(columnNamesWriter).build();
}
#Bean
public Step readAccessDb(StepBuilderFactory stepBuilderFactory, ItemReader<WeeklyStoreItem> importReader, ItemWriter<WeeklyStoreItem> weeklyStoreItemWriter, PlatformTransactionManager legacyTransactionManager) {
return stepBuilderFactory.get("readAccessDb").<WeeklyStoreItem, WeeklyStoreItem> chunk(chunkSize).reader(importReader).writer(weeklyStoreItemWriter).transactionManager(legacyTransactionManager).build();
}
...
At any point in the job execution if any step fails, I want to be able to exit out and do two things:
Move the file being processed to specfic folder.
Have the scheduler know via the program exit code that an error occurred.
Right now, the job will exit when an uncaught exception occurs, which is partially what I want, but Spring handles the exception, logs it, and then exits gracefully back to my main method. At that point, I'm not sure how to capture whether the job run was truly successful.
I had the same problem. I managed to solve partially like this (Dave Sayer outlined this idea already in comment).
First I created this created listener:
import org.springframework.batch.core.ExitStatus;
import org.springframework.boot.autoconfigure.batch.JobExecutionEvent;
import org.springframework.context.ApplicationListener;
public class JobResultListener implements
ApplicationListener<JobExecutionEvent> {
private ExitStatus jobExitStatus;
public ExitStatus getJobExitStatus() {
return jobExitStatus;
}
#Override
public void onApplicationEvent(JobExecutionEvent event) {
jobExitStatus = event.getJobExecution().getExitStatus();
}
}
Than I used it to get job execution status this way:
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication springApplication =
new SpringApplication(Application.class);
JobResultListener jobResultListener = new JobResultListener();
springApplication.addListeners(jobResultListener);
springApplication.run(args);
if (!ExitStatus.COMPLETED.equals(jobResultListener.getJobExitStatus())) {
throw new IllegalStateException("Job failed");
}
}
}
But for some reason I wasn't able to get instances of exceptions from injected event. But as you mentioned, error is logged into app logs so this mechanism was enough for me to inform called of my process about error with error code != 0.
BTW, I observed that some error penetrated from springApplication.run(args) call, but not sure why not all. Also I didn't find anything mentioned in Spring Boot docs