My application is written with Spring, Hibernate (JPA), JBOSS 9.0.0.GA & JBOSS EAP 6.4. In POM.xml I have specified the packaging to WAR.
I have 2 functions which I'd like to automate:
a. CSV reader - Read from CSV file and update table in DB
package com.fwd.pmap.memberInterfaceFile;
/* all imports */
public class CsvReader
{
public void importInterfaceFile() throws Exception
{
// do processing here
}
}
b. CSV Writer - Read from DB and output to CSV file
package com.fwd.pmap.memberInterfaceFile;
/* all imports */
public class CsvWriter
{
public void generateInterfaceFile() throws Exception
{
// do processing here
}
}
How can I automate both functions above to run on a specific time every day? For example:
CSV Reader to run daily # 05:00 AM
CSV Writer to run daily # 07:00 AM
Project Structure
Finally decided to use Spring Scheduling as it does not involve lots of coding as well as XML.
This is the bean class where I schedule 2 jobs to run at 5AM & 6AM daily:
package com.fwd.pmap.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.fwd.pmap.memberInterfaceFile.CsvReader;
import com.fwd.pmap.memberInterfaceFile.CsvWriter;;
#Component
public class MyBean {
#Scheduled(cron="0 0 5 * * *")
public void importInterfaceFile()
{
CsvReader reader = new CsvReader();
try {
reader.importInterfaceFile();
} catch (Exception e) {
e.printStackTrace();
}
}
#Scheduled(cron="0 0 6 * * *")
public void generateInterfaceFile()
{
CsvWriter writer = new CsvWriter();
try {
writer.generateInterfaceFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Here's the config class:
package com.fwd.pmap.scheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import com.fwd.pmap.scheduler.MyBean;
#Configuration
#EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
#Bean
public MyBean bean() {
return new MyBean();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(4);
}
}
And the main class to execute the above:
package main;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import com.fwd.pmap.scheduler.SchedulerConfig;
public class Main {
static Logger LOGGER = LoggerFactory.getLogger(Main.class);
#SuppressWarnings({ "unused", "resource" })
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(SchedulerConfig.class);
}
}
Related
I have just started with Java Spring and am getting familiar with the framework.
Let's say I have a controller with two endpoints
"/remove_old"
"/remove_new"
They do the same job: controller layer -> service layer -> DAO except for databases which should be used in dao methods - those are different. As I understand, this can be nicely handled by Spring with no change in the service layer. How should I organize my beans to make it the most appropriate way? The only solution I can think of so far is to autowire everything and then expose Dao::setDatabase method which would be called at the controller layer.
Here is a spring-boot3, ARD-solution:
(starter-used)
Simple entity:
package com.example.routingds.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Data;
#Data
#Entity
class SomeEntity {
#Id
#GeneratedValue
Long id;
}
Plus repo:
package com.example.routingds.demo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {}
An enum (for all our data sources/tenants):
package com.example.routingds.demo;
public enum MyTenant {
OLD, NEW;
}
A thingy like (ref1, ref2, ref3...):
package com.example.routingds.demo;
public class MyTenantThreadLocalContextHolder {
private static ThreadLocal<MyTenant> threadLocal = new ThreadLocal<>();
public static void set(MyTenant tenant) {
threadLocal.set(tenant);
}
public static MyTenant get() {
return threadLocal.get();
}
}
...(we have plenty options here, but this is thread safe & easy to test/static!)
Then a (very) simple controller like:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
#Controller
public class DemoController {
#Autowired
private SomeRepository someRepository;
#DeleteMapping("/remove_new/{id}")
public void removeNew(#PathVariable Long id) {
removeInternal(NEW, id);
}
#DeleteMapping("/remove_old/{id}")
public void removeOld(#PathVariable Long id) {
removeInternal(OLD, id);
}
private void removeInternal(MyTenant current, Long id) {
// set context ...
MyTenantThreadLocalContextHolder.set(current);
// and "just delete" (ard+context will choose correct DS):
someRepository.deleteById(id);
}
}
Lets go wire it:
application.properties:
fallback.datasource.url=jdbc:h2:./data/fallback
fallback.datasource.username=sa
fallback.datasource.password=
#fallback.datasource. ... more if you like/need
old.datasource.url=jdbc:h2:./data/old
old.datasource.username=sa
old.datasource.password=
# ...
new.datasource.url=jdbc:h2:./data/new
new.datasource.username=sa
new.datasource.password=
# ...
# assuming all dbs initialized , otherwise set these (+ un-comment main class):
#spring.sql.init.mode=always
#spring.sql.init.continue-on-error=false
# debug:
spring.jpa.show-sql=true
spring.h2.console.enabled=true
# https://github.com/spring-projects/spring-data-jpa/issues/2717:
spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED
App/Main/Config:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
//import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
//import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
#SpringBootApplication
public class RoutingDsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingDsDemoApplication.class, args);
}
// load the props:
#Bean
#Primary // one should be primary ...
#ConfigurationProperties("fallback.datasource")
public DataSourceProperties fallbackDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("old.datasource")
public DataSourceProperties oldDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("new.datasource")
public DataSourceProperties newDSProps() {
return new DataSourceProperties();
}
// the main (abstract routing) data source:
#Bean
#Primary
public DataSource dataSource(
#Qualifier("masterDS") DataSource masterDS,
#Qualifier("newDS") DataSource newDS,
#Qualifier("oldDS") DataSource oldDS) {
return new AbstractRoutingDataSource() {
{ // inline instance:
setTargetDataSources( // ! we operationally use only OLD, NEW:
Map.of(
// lookup key, data source:
OLD, oldDS,
NEW, newDS
)
);
//... but as a default/fallback/no-context:
setDefaultTargetDataSource(masterDS);
afterPropertiesSet();
}
// inline override:
#Override
protected Object determineCurrentLookupKey() {
return MyTenantThreadLocalContextHolder.get();
}
};
}
// the "slaves" / underlying / your DS's:
#Bean // default/master/backup/unused:
DataSource masterDS(DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource oldDS(#Qualifier("oldDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource newDS(#Qualifier("newDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
// for (script) db initialization, we might need this (+'sql.init.mode=always'):
// #Bean
// DataSourceScriptDatabaseInitializer initOld(#Qualifier("oldDS") DataSource oldDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(oldDS, settings);
// }
//
// #Bean
// DataSourceScriptDatabaseInitializer initNew(#Qualifier("newDS") DataSource newDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(newDS, settings);
// }
}
Init schema (src/main/resources/schema.sql):
create table some_entity (id bigint not null, primary key (id));
create sequence some_entity_seq start with 1 increment by 50;
TESTING TIME!! :))
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#SpringBootTest
#AutoConfigureMockMvc // we will test the controller!
class RoutingDsDemoApplicationTests {
// tenant/DS dependent test IDs:
static final EnumMap<MyTenant, Set<Long>> TEST_IDS = new EnumMap<>(
Map.of(
OLD, new HashSet<>(),
NEW, new HashSet<>()
)
);
#TestConfiguration // some "test setup":
static class TestDataConfig {
#Bean
InitializingBean testData(SomeRepository repo) { // <- normal/autowired repo
return () -> {
// for OLD and NEW (tenant):
Arrays.stream(MyTenant.values())
.forEach((t) -> {
// set context/db:
MyTenantThreadLocalContextHolder.set(t);
// clean up (we shouldn't need this/DANGER):
// repo.deleteAll();
// save 100 SomeEntity's, and store ids to TEST_IDS:
IntStream.range(0, 100).forEach((i) -> {
TEST_IDS.get(t).add(
repo.save(new SomeEntity()).getId()
);
});
});
};
}
}
#Autowired
MockMvc mockMvc;
#Autowired
SomeRepository helper;
#Test
void testRemoveOld() {
// for each (known) OLD id:
TEST_IDS.get(OLD).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_old/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(OLD);
TEST_IDS.get(OLD).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
#Test
void testRemoveNew() {
// for each (known) NEW id:
TEST_IDS.get(NEW).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_new/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(NEW);
TEST_IDS.get(NEW).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
}
passes:
Results:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
See also:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-access.configure-two-datasources
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization
I have encountered an interesting yet a terrifying situation. My application has two features namely, OtpListener and CdmMonitor running on two separate threads. For now, only the OtpListener feature was required so I commented out CdmMonitor in the main class.
However, I still see the logs for the commented out class CdmMonitorService. This service was running (when it shouldn't as it was commented)
Do threads implemented from the Runnable class operate this way? I did not find anything of the sort in its documentation.
Main Class: OtpTrackerApp.java
package dev.xfoil.otpTracker;
import dev.xfoil.otpTracker.service.CdmMonitorService;
import dev.xfoil.otpTracker.service.OTPListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan({"dev.xfoil.otpTracker.*","dev.xfoil.shared.*", "dev.xfoil.shared.dal.repositories.springdata.*", "dev.xfoil.shared.dal.entities.*","dev.xfoil.shared.dal.cache.*"})
public class OtpTrackerApp implements CommandLineRunner {
#Autowired
public OTPListener otpListener;
// #Autowired
// public CdmMonitorService cdmMonitorService;
public static void main(String[] args){
SpringApplication.run(OtpTrackerApp.class, args);
}
#Override
public void run(String... args) throws Exception {
// Thread monitoringThread = new Thread(cdmMonitorService);
// monitoringThread.start();
otpListener.startListening();
}
}
CdmMonitorService.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.otpTracker.firebase.FirestoreManager;
import dev.xfoil.shared.dal.repositories.springdata.MachineMonitorRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachineOperationsHistoryRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachinesRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
#Service
public class CdmMonitorService implements Runnable {
FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
MachineMonitorRepo machineMonitorRepo;
#Autowired
MachineOperationsHistoryRepo machineOperationsHistoryRepo;
#Autowired
MachinesRepo machinesRepo;
#Autowired
FirestoreManager firestoreManager;
public CdmMonitorService() {
}
#Override
#Scheduled(fixedDelay = 120000l)
public void run() {
try {
// code removed for brevity
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
logger.atWarning().log("Exception in CDM Monitor Thread " + e);
}
}
}
OtpListener.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.shared.dal.entities.AccountLedger;
import dev.xfoil.shared.dal.models.OTP;
import dev.xfoil.shared.dal.repositories.springdata.LedgerRepo;
import dev.xfoil.shared.dal.repositories.springdata.NoteCountRepo;
import dev.xfoil.shared.dal.repositories.springdata.UtilRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
#Service
public class OTPListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
UtilRepo utilRepo;
#Autowired
ThirdPartyApiCalls discordServer;
#Autowired
NoteCountRepo noteCountRepo;
private static LocalDateTime latestOTPTime;
#Value("#{${webhooks}}")
HashMap<String, String> discordLink = new HashMap<String, String>();
#PostConstruct
public LocalDateTime getLatestOTPTimestamp() {
latestOTPTime = utilRepo.getTimeOfLatestOTP();
return latestOTPTime;
}
public void startListening() throws InterruptedException {
logger.atInfo().log("Current in-mem links:");
try {
// code commented for brevity
Thread.sleep(5 * 60 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Have an openapi yml file with contains a simple get request with response, from this yml have the java files below been generated (among others).
How should these java files be used?
How to hook into the generated files?
Could simply copy the generated main and controller class into the main source tree, but does not seem to be the correct way.
--- edit ---
In the generated Controller class, how do I override the default responses that exist in the interface ExampleApi? Without having to modify the generated Controller class and have it in the VCS.
--- edit ---
build.gradle.kts
...
openApiGenerate {
generatorName.set("spring")
inputSpec.set("$rootDir/specs/api-example.yml")
outputDir.set("$buildDir/generated")
apiPackage.set("com.example.openapi.generated.api")
invokerPackage.set("com.example.openapi.generated.invoker")
modelPackage.set("com.example.openapi.generated.model")
configOptions.set(mapOf(
"dateLibrary" to "java8"
))
systemProperties.set(mapOf(
"modelDocs" to "false"
))
}
...
What should be done with these classes?
package com.example.openapi.generated.invoker;
import com.fasterxml.jackson.databind.Module;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication
#ComponentScan(basePackages = {"com.example.openapi.generated.invoker", "com.example.openapi.generated.api" , "org.openapitools.configuration"})
public class OpenAPI2SpringBoot implements CommandLineRunner {
#Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
new SpringApplication(OpenAPI2SpringBoot.class).run(args);
}
static class ExitException extends RuntimeException implements ExitCodeGenerator {
private static final long serialVersionUID = 1L;
#Override
public int getExitCode() {
return 10;
}
}
#Bean
public WebMvcConfigurer webConfigurer() {
return new WebMvcConfigurer() {
/*#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("Content-Type");
}*/
};
}
#Bean
public Module jsonNullableModule() {
return new JsonNullableModule();
}
}
package com.example.openapi.generated.api;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Optional;
#Controller
#RequestMapping("${openapi.basic.base-path:}")
public class ExampleApiController implements ExampleApi {
private final NativeWebRequest request;
#org.springframework.beans.factory.annotation.Autowired
public ExampleApiController(NativeWebRequest request) {
this.request = request;
}
#Override
public Optional<NativeWebRequest> getRequest() {
return Optional.ofNullable(request);
}
}
You need to tell gradle to compile the files, there is no need to copy them.
Add the path with the generated files to the sourceSets of your project. Something like this:
sourceSets {
main {
java {
srcDir("$buildDir/generated")
}
}
}
I have a Spring-Boot application which is going to be an orchestration service for several other processes we want to trigger. I have it currently set up using Spring Scheduling pulling crons dynamically from a database. I threw in a rest method to trigger the process to pull new cron information from the database. This logic all works correctly. The only "issue" is that it doesn't use the new cron information until the next scheduled run which gets to the real question. Is there a way to interrupt the current Trigger and schedule one again using the updated cron information. Here is the application for reference:
package com.bts.poc;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
#SpringBootApplication
#EnableScheduling
#RestController
#RequestMapping("/APSCommon/Scheduling")
public class Application implements SchedulingConfigurer {
#Autowired
private DynamicCron dynamicCron;
#Autowired
PropertyManager propertyManager;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class);
}
private String cronConfig() {
String cronTabExpression = propertyManager.getProperty("COMPANY", "JOB_NAME","CRON_EXPRESSION");
return cronTabExpression;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
#Override
public void run() {
dynamicCron.runJob();
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String cron = cronConfig();
CronTrigger trigger = new CronTrigger(cron);
Date nextExec = trigger.nextExecutionTime(triggerContext);
DynamicCron.cronExpression = cron;
return nextExec;
}
});
}
#RequestMapping(value = "/reloadScheduling", method = RequestMethod.GET)
public String reloadScheduling() {
PropertyManager.setResetProperties(true);
return "schedules will be altered next run";
}
}
So using SchedulingConfigurer->configureTasks you can't get access to the ScheduledFuture(s) in the Spring version I am using (4.2.7.RELEASE). From several posts I have read it has been mentioned as possible functionality for the future. I got around this by doing the following:
package com.bts.poc;
import com.bts.poc.service.DynamicCron;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
#SpringBootApplication(exclude = MessageSourceAutoConfiguration.class)
#EnableScheduling
#RestController
public class Application extends SpringBootServletInitializer {
#Autowired
private DynamicCron dynamicCron;
#Autowired
private PropertyManager propertyManager;
private static List<ScheduledFuture> scheduledFutures = new ArrayList<>();
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
private static TaskScheduler scheduler;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
private String cronConfig() {
return propertyManager.getProperty("COMPANY", "JOB_NAME", "CRON_EXPRESSION");
}
#RequestMapping(value = {"scheduling/start"}, method = RequestMethod.GET)
public #ResponseBody String startScheduling() {
scheduleAll();
LOGGER.info("Scheduling of jobs has been started.");
return "Scheduling of jobs has been started.";
}
#RequestMapping(value = {"scheduling/cancel"}, method = RequestMethod.GET)
public #ResponseBody String cancelScheduling() {
cancelAll();
LOGGER.info("Cancelling all scheduled jobs.");
return "Cancelling all scheduled jobs.";
}
private void scheduleAll() {
LOGGER.info("Scheduling all applications to run.");
cancelAll();
//eventually go through the database and load all jobs to be scheduled here.
schedule(cronConfig());
}
/**
* Cancel all the scheduled reports
*/
private void cancelAll() {
for (ScheduledFuture scheduledFuture : scheduledFutures) {
scheduledFuture.cancel(true);
}
scheduledFutures.clear();
}
/**
* Schedule the scheduled report with the given cron schedule information
*/
private void schedule(String cronSchedule) {
TimeZone tz = TimeZone.getDefault();
LOGGER.info("Setting up application {} to execute with cron string: '{}'.", cronSchedule);
CronTrigger trigger = new CronTrigger(cronSchedule, tz);
scheduler = scheduler();
if (scheduler == null) {
LOGGER.error("Unable to schedule job as scheduler was not found");
return;
}
ScheduledFuture<?> future = scheduler.schedule(new DynamicCron(), trigger);
scheduledFutures.add(future);
}
#Bean
public TaskScheduler scheduler() {
if (scheduler == null) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.afterPropertiesSet();
}
return scheduler;
}
}
This basically replicates the functionality the ScheduledTaskRegistrar provides allowing you manage the ScheduledFuture(s). Hopefully this can help someone else in the future.
I want to execute a java class (which contains a java thread I want to execute) after spring boot starts. My initial code:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And here is is the code I want to execute at start:
public class SimularProfesor implements Runnable{
// Class atributes
// Constructor
public SimularProfesor() {
//Initialization of atributes
}
#Override
public void run() {
while(true) {
// Do something
}
}
}
How can I call this thread? This is what I'm supposed to do:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// Call thread (constructor must be executed too)
}
}
Don't mess around with threads yourself. Spring (and also plain Java) has a nice abstraction for that.
First create a bean of the type TaskExecutor in your configuration
#Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor(); // Or use another one of your liking
}
Then create a CommandLineRunner (although an ApplicationListener<ContextRefreshedEvent> would also work) to schedule your task.
#Bean
public CommandLineRunner schedulingRunner(TaskExecutor executor) {
return new CommandLineRunner() {
public void run(String... args) throws Exception {
executor.execute(new SimularProfesor());
}
}
}
You could of course make also your own class managed by spring.
Advantage of this is that Spring will also cleanup the threads for you and you don't have to think about it yourself. I used a CommandLineRunner here because that will execute after all beans have bean initialized.
Main Class SpringBoot
#SpringBootApplication
#EnableAsync
#Controller
public class ...
Example Class Controler
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
#Component
public class ExecutorBase {
private static final Logger log = LoggerFactory.getLogger(ExecutorBase.class);
#Autowired
private TaskExecutor taskExecutor;
#Autowired
private ApplicationContext applicationContext;
private Boolean debug = true;
#PostConstruct
public void atStartup() {
ClasseTaskRunn classeTaskRunn = applicationContext.getBean(ClasseTaskRunn.class);
taskExecutor.execute(classeTaskRunn );
if (debug) {
log.warn("###### Startup ok");
}
}
}
Example Class Task Runnable
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope("application")
public class ClasseTaskRunn implements Runnable {
private static final Logger log = LoggerFactory.getLogger(ClasseTaskRunn.class);
#Autowired
ClasseDAO classeDAO;
#Override
public void run() {
longBackgorund();
}
protected void longBackgorund() {
while (test) {
if (debug) {
log.warn("###### DEBUG: " ... );
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}