I am playing with a simple Spring Boot application and RabbitMQ.
However I cannot figure out how to run a method periodically.
Here is my Application class
#SpringBootApplication
public class SampleApp {
#Autowired
Sender sender;
public static void main(String[] args) {
SpringApplication.run(SampleApp.class, args);
}
#EventListener(ApplicationReadyEvent.class)
public void doSomethingAfterStartup() {
sender.sendMessage();
}
}
And the sendMessage method is defined as below
#Scheduled(fixedRate = 3000L)
public void sendMessage() {
log.info("Sending message...");
rabbitTemplate.convertAndSend("my-exchange", "my-routing-key", "TEST MESSAGE");
}
However this method is called only once, I can see only a single line in the console.
What I missed in my code?
Thanks.
Looks like you are missing #EnableScheduling:
#EnableScheduling
#SpringBootApplication
public class SampleApp {
...
}
Quoting the documentation:
Enables Spring's scheduled task execution capability, similar to functionality found in Spring's <task:*> XML namespace. To be used on #Configuration classes as follows:
#Configuration
#EnableScheduling
public class AppConfig {
// various #Bean definitions
}
This enables detection of #Scheduled annotations on any Spring-managed bean in the container.
I am usually using the Spring ThreadPoolTaskScheduler. You define it, as Bean for example then you wrap your method into a Runnable and you call it at intervals defined by a CronTrigger. The result can be retrieved using a ScheduledFuture
Check https://www.baeldung.com/spring-task-scheduler for a complete beginner tutorial.
Here's an alternative way if you don't want to rely on Spring's scheduler.
It's using rxjava2, here's an example:
#Component
public class MessagePublisher {
Sender sender;
Disposable d;
#Autowired
public Config(Sender sender) {
this.sender = sender;
this.d = doSomethingAfterStartup();
}
public Disposable doSomethingAfterStartup() {
return Observable.interval(3000, TimeUnit.MILLISECONDS).subscribe(tick -> {
sender.sendMessage();
});
}
}
I wanted to make a method writing to DB as async using #Async annotation.
I marked the class with the annotation #EnableAsync:
#EnableAsync
public class FacialRecognitionAsyncImpl {
#Async
public void populateDataInPushQueue(int mediaId, int studentId) {
//myCode
}
}
while calling the populateDataInPushQueue method, the write operation should be executed in another thread and the flow should continue from the class I am calling this method. But this is not happening and the program execution is waiting for this method to complete.
The #Async annotation has few limitations - check whether those are respected:
it must be applied to public methods only
it cannot be called from the same class as defined
the return type must be either void or Future
The following can be found at the documentation of #EnableAsync:
Please note that proxy mode allows for the interception of calls through the proxy only; local calls within the same class cannot get intercepted that way.
Another fact is that the class annotated with #EnableAsync must be a #Configuration as well. Therefore start with an empty class:
#EnableAsync
#Configuration
public class AsyncConfiguration { }
In my opinion, you are missing #Configuration annotation and your async service is not component scanned. Here is an example code fragment that should do the trick:
#Configuration
#EnableAsync //should be placed together.
public class FacialRecognitionAsyncService {
#Async
public void populateDataInPushQueue(int mediaId, int studentId) {
//myCode
}
}
#Configuration
#EnableAsync
public class FacialServiceConfig {
// this will make your service to be scanned.
#Bean
public FacialRecognitionAsyncService createFacialRecognitionService() {
return new FacialRecognitionAsyncService();
}
}
Now the service bean that is invoking the async method. Notice that it has been dependency injected. This way spring AOP proxies will be invoked on each invocation fo the facialService. Spring uses AOP in the back scenes in order to implement #Async.
#Service
public class MyBusinessService {
#Autowire
FacialRecognitionAsyncService facialService;
public myBusinessMethod() {
facialService.populateDataInPushQueue()
}
Notice that FacialService is injected in MyService through dependency injection.
I want to be able to run the SAME scheduled job in Spring.
After searching on the Internet, I figured out how to run multiple different jobs at the same time.
I have a #Service annotated class which has only one method, annotated with #Scheduled. I want to have multiple instances of this job running at the same time.
I am not using Quartz or Spring Batch( I have seen a lot of examples with Spring Batch).
The documentation doesn't clearly say if this can be achieved.
Yes, it can be easily achieved, but not with #Scheduled annotation.
How? Let me first explain how Spring works.
Spring from every method annotated with #Scheduled creates a new Runnable object and then schedules it for execution to the TaskScheduler (ThreadPoolTaskScheduler to be precise).
To see the exact code look at ScheduledAnnotationBeanPostProcessor.processScheduled().
So to fulfill your requirement: have multiple instances of the same job, but without using Quartz or Spring Batch we need to abandon #Scheduled annotation and do something a bit different than what the ScheduledAnnotationBeanPostProcessor does by default.
#Configuration
public class SpringConfig {
#Autowired
private TaskScheduler scheduler;
#Autowired
private YourServiceAnnotatedClass service;
#PostConstruct
public void startJobs() {
int numOfJobInstances = 3;
List<ImportantJob> jobs = IntStream.range(0, numOfJobInstances)
.mapToObj(i -> new ImportantJob("job" + i, service))
.collect(Collectors.toList());
jobs.forEach(this::schedule);
}
private void schedule(ImportantJob job) {
scheduler.schedule(job, new CronTrigger("*/5 * * * * *"));
// Above CronTrigger with 5 seconds was used, but feel free to use other variants, e.g.
// scheduler.scheduleAtFixedRate()
// scheduler.scheduleWithFixedDelay()
}
#Bean(destroyMethod = "shutdown")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(3); // Adjust it to your needs
return taskScheduler;
}
}
class ImportantJob implements Runnable {
private final String name;
private final YourServiceAnnotatedClass service;
public ImportantJob(String name, YourServiceAnnotatedClass service) {
this.name = name;
this.service = service;
}
#Override
public void run() {
service.doSth();
}
}
As you can see, although #Scheduled is useful and simple, it is not very flexible. But with some effort you can gain much more control over scheduled tasks.
I have a code which runs at a regular interval.
Below is code for that
#EnableScheduling
#Component
public class Cer {
#Autowired
private A a;
#Autowired
private CacheManager cacheManager;
#Scheduled(fixedDelayString = "${xvc}")
public void getData() {
getCat();
getB();
return;
}
}
I want to change#Scheduled(fixedDelayString = "${xvc}") this based on a boolean say runOnce if runOnce is true #scheduled should run once only say on code startup.
Any advice how to achieve this.
Thanks in advance.
I would place the functionality that you want to schedule in a component:
#Component
public class ServiceToSchedule {
public void methodThatWillBeScheduled() {
// whatever
return;
}
}
And have two additional components that will be instantiated depending on a Profile. One of them schedules the task, and the other one just executes it once.
#Profile("!scheduled")
#Component
public class OnceExecutor {
#Autowired
private ServiceToSchedule service;
#PostConstruct
public void executeOnce() {
// will just be execute once
service.methodThatWillBeScheduled();
}
}
#Profile("scheduled")
#Component
#EnableScheduling
public class ScheduledExecutor {
#Autowired
private ServiceToSchedule service;
#Scheduled(fixedRate = whateverYouDecide)
public void schedule() {
// will be scheduled
service.methodThatWillBeScheduled();
}
}
Depending on which profile is active, your method will be executed just once (scheduled profile is not active), or will be scheduled (scheduled profile is active).
You set the spring profiles by (for example) starting your service with:
-Dspring.profiles.active=scheduled
I am defining scheduled jobs with cron style patterns in Spring, using the #Scheduled annotation.
The cron pattern is stored in a config properties file. Actually there are two properties files: one default config, and one profile config that is environment dependent (e.g. dev, test, prod customer 1, prod customer 2 etc.) and overrides some of the default values.
I configured a property placeholder bean in my spring context which allows me to use ${} style placeholders to import values from my properties files.
The job beans looks like this:
#Component
public class ImagesPurgeJob implements Job {
private Logger logger = Logger.getLogger(this.getClass());
#Override
#Transactional(readOnly=true)
#Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
public void execute() {
//Do something
//can use DAO or other autowired beans here
}
}
Relevant parts of my context XML :
<!-- Enable configuration of scheduled tasks via annotations -->
<task:annotation-driven/>
<!-- Load configuration files and allow '${}' style placeholders -->
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/default-config.properties</value>
<value>classpath:config/environment-config.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="ignoreResourceNotFound" value="false"/>
</bean>
I really like this. It's quite simple and clean with minimal XML.
However I have one more requirement: some of these jobs can be totally disabled in some cases.
So, before I used Spring to manage them I created them manually and there is a boolean parameter along with the cron parameter in the config files, to specify if the job has to be enabled or not:
jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
How can I use this parameter in Spring to conditionally create or just plainly ignore the bean, depending on this config parameter?
One obvious workaround would be to define a cron pattern that would never evaluate, so the job is never executed. But the bean would still be created and the config would be a bit obscure, so I feel there must be a better solution.
The most efficient way to disable #Scheduled in Spring is to set cron expression to -
#Scheduled(cron = "-")
public void autoEvictAllCache() {
LOGGER.info("Refresing the Cache Start :: " + new Date());
activeMQUtility.sendToTopicCacheEviction("ALL");
LOGGER.info("Refresing the Cache Complete :: " + new Date());
}
From the docs:
CRON_DISABLED
public static final String CRON_DISABLED
A special cron
expression value that indicates a disabled trigger: "-". This is
primarily meant for use with ${...} placeholders, allowing for
external disabling of corresponding scheduled methods.
Since:
5.1 See Also: ScheduledTaskRegistrar.CRON_DISABLED
#Component
public class ImagesPurgeJob implements Job {
private Logger logger = Logger.getLogger(this.getClass());
#Value("${jobs.mediafiles.imagesPurgeJob.enable}")
private boolean imagesPurgeJobEnable;
#Override
#Transactional(readOnly=true)
#Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
public void execute() {
//Do something
//can use DAO or other autowired beans here
if(imagesPurgeJobEnable){
Do your conditional job here...
}
}
}
You can group schedule methods by conditions into number of services and init them like this:
#Service
#ConditionalOnProperty("yourConditionPropery")
public class SchedulingService {
#Scheduled
public void task1() {...}
#Scheduled
public void task2() {...}
}
Spring Boot provides #ConditionalOnProperty, which would be perfect if you were using Spring Boot. This annotation is a specialization of #Conditional, introduced with Spring 4.0.0.
Assuming you're just using "regular" spring and not Spring Boot, you could create your own Condition implementation for use with #Conditional that would mimic Spring Boot's #ConditionalOnProperty.
If you are looking to toggle #EnableScheduling from a property you can do this in Spring Boot by moving the #EnableScheduling annotation to a configuration class and use #ConditionalOnProperty as follows:
#Configuration
#EnableScheduling
#ConditionalOnProperty(prefix = "com.example.scheduling", name="enabled", havingValue="true", matchIfMissing = true)
public class SchedulingConfiguration {
}
This will disable scheduling for the application. This may be useful in a situation where you want to be able to run the application once or scheduled depending on how it's being started.
From wilkinsona's comment on here: https://github.com/spring-projects/spring-boot/issues/12682
Your question states to condition the actual creation of the bean. You can do this easily with this parameter by using #Profile if you are using at least Spring 3.1.
See the documentation here: http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/context/annotation/Profile.html
You can also create a Bean based on condition and that Bean can have a Scheduled method.
#Component
#Configuration
#EnableScheduling
public class CustomCronComponent {
#Bean
#ConditionalOnProperty(value = "my.cron.enabled", matchIfMissing = true, havingValue = "true")
public MyCronTask runMyCronTask() {
return new MyCronTask();
}
}
and
#Component
public class MyCronTask {
#Scheduled(cron = "${my.cron.expression}")
public void run() {
String a = "";
}
}
#Component
public class CurrencySyncServiceImpl implements CurrencySyncService {
private static Boolean isEnableSync;
/**
* Currency Sync FixedDelay in minutes
*/
private static Integer fixedDelay;
#Transactional
#Override
#Scheduled(fixedDelayString = "#{${currency.sync.fixedDelay}*60*1000}")
public void sync() {
if(CurrencySyncServiceImpl.isEnableSync) {
//Do something
//you can use DAO or other autowired beans here.
}
}
#Value("${currency.sync.fixedDelay}")
public void setFixedDelay(Integer fixedDelay) {
CurrencySyncServiceImpl.fixedDelay = fixedDelay;
}
#Value("${currency.sync.isEnable}")
public void setIsEnableSync(Boolean isEnableSync) {
CurrencySyncServiceImpl.isEnableSync = isEnableSync;
}
}
Please see my answer in another question. I think this is the best way to solve it.
How to stop a scheduled task that was started using #Scheduled annotation?
Define a custom annotation like below.
#Documented
#Retention (RUNTIME)
#Target(ElementType.TYPE)
public #interface ScheduledSwitch {
// do nothing
}
Define a class implements org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.
public class ScheduledAnnotationBeanPostProcessorCustom
extends ScheduledAnnotationBeanPostProcessor {
#Value(value = "${prevent.scheduled.tasks:false}")
private boolean preventScheduledTasks;
private Map<Object, String> beans = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock(true);
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
ScheduledSwitch switch = AopProxyUtils.ultimateTargetClass(bean)
.getAnnotation(ScheduledSwitch.class);
if (null != switch) {
beans.put(bean, beanName);
if (preventScheduledTasks) {
return bean;
}
}
return super.postProcessAfterInitialization(bean, beanName);
}
public void stop() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
postProcessBeforeDestruction(entry.getKey(), entry.getValue());
}
} finally {
lock.unlock();
}
}
public void start() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
if (!requiresDestruction(entry.getKey())) {
super.postProcessAfterInitialization(
entry.getKey(), entry.getValue());
}
}
} finally {
lock.unlock();
}
}
}
Replace ScheduledAnnotationBeanPostProcessor bean by the custom bean in configuration.
#Configuration
public class ScheduledConfig {
#Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
return new ScheduledAnnotationBeanPostProcessorCustom();
}
}
Add #ScheduledSwitch annotation to the beans that you want to prevent or stop #Scheduled tasks.
I know my answer is a hack, but giving a valid cron expression that never executes may fix the issue (in the environment specific configuration), Quartz: Cron expression that will never execute
We can disable the bean creation of the class having that scheduled methods using #Conditional annotation. This is very similar to #ConditionalOnProperty. This is used to conditionally spin up a bean on to the spring context. If we set the value to false, then the bean will not be spun up and loaded to spring. Below is the code.
application.properties:
com.boot.enable.scheduling=enable
Condition:
public class ConditionalBeans implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "enabled".equalsIgnoreCase(context.getEnvironment().getProperty("com.boot.enable.scheduling"));
}
}
My schedule class
#Service
#Conditional(ConditionalSchedules.class)
public class PrintPeriodicallyService {
#Scheduled(fixedRate = 3000)
public void runEvery3Seconds() {
System.out.println("Current time : " + new Date().getTime());
}
}
This approach has a lot of flexibility where the condition generation is totally under our control.