Spring boot: How to parameterize #Scheduled - java

I am new to Spring and have only scratched the surface of what can be done with it.
I have a situation where I need to set up a recurring task using the #Scheduled annotation. The rate is specified as a member field in an object that is passed to the class encapsulating the method representing the task.
I've used the mechanism that allows for accessing the configuration or environment, e.g. #Scheduled(fixedRateString = "${some.property:default}"); this works great.
What I don't know how to do is insert the value from an object into the #Scheduled.
For example:
class MyClass {
private MyObject myObj;
public MyClass(MyObject myObj) {
this.myObj = myObj;
}
#Scheduled(fixedRateString = "${myObj.rate:5000}")
private void someTask() {
...
}
}
The code above, of course, does not work, I'm just giving an example of what I'm trying to do.
Any suggestions would be appreciated.

Yes you can use the #Scheduled annotation to do that with a SpEL expression (available on the #Scheduled annotation since Spring 4.3.x). Here's an example:
#Slf4j
#Configuration
#EnableScheduling
public class ExampleClass {
static class ScheduleCalculator {
public String calc() {
return "5000";
}
}
#Bean("scheduleCalculator")
public ScheduleCalculator createScheduleCalculator() {
return new ScheduleCalculator();
}
#Scheduled(fixedRateString = "#{scheduleCalculator.calc()}")
public void someTask() {
log.info("Hello world");
}
}
However, just because you can do it like this doesn't mean you necessarily should.
Your code may be easier to follow to folks that have to maintain it in the future if you use the spring task scheduler plus you get control of the thread pool used for scheduling instead of relying on the shared executor that all #Scheduled tasks get lumped into.

Unfortunately the spring bean creation process will not read local variables like that.
You can use the Spring TaskScheduler class.
Essentially you just have to define a thread pool that you will use to run the scheduled tasks (as a bean) and run taskScheduler.schedule(runnable, new CronTrigger("* * * * *")). There is a detailed example here:
https://www.baeldung.com/spring-task-scheduler

You can do like follow:
#Component
#ConfigurationProperties(prefix = "my.obj")
public class MyObject {
private String cronExecExpr = "*/5 * * * * *";
// getter and setter
}
class MyClass {
private MyObject myObj;
public MyClass(MyObject myObj) {
this.myObj = myObj;
}
#Scheduled(cron = "${my.obj.cron-exec-expr:*/5 * * * * *}")
private void someTask() {
...
}
}

As you can see her : https://www.baeldung.com/spring-scheduled-tasks
You can do that like follow :
A fixedDelay task:
#Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}")
A fixedRate task:
#Scheduled(fixedRateString = "${fixedRate.in.milliseconds}")
A cron expression based task:
#Scheduled(cron = "${cron.expression}")

Related

Annotation #Value doesn't work properly in Spring Boot?

CONTEXT:
I process reports with #Scheduled annotation and when invoke Component from Service property not getting initialized with #Value annotation even it physically exists in .properties and printed out in #PostConstruct.
DESCRIPTION:
ReportProcessor interface and InventoryReportProcessor implementation:
#FunctionalInterface
interface ReportProcessor {
public void process(OutputStream outputStream);
}
#Component
public class InventoryReportProcessor implement ReportProcessor {
#Value("${reportGenerator.path}")
private String destinationFileToSave;
/*
#PostConstruct
public void init() {
System.out.println(destinationFileToSave);
}
*/
#Override
public Map<String, Long> process(ByteArrayOutputStream outputStream) throws IOException {
System.out.println(destinationFileToSave);
// Some data processing in here
return null;
}
}
I use it from
#Service
public class ReportService {
#Value("${mws.appVersion}")
private String appVersion;
/* Other initialization and public API methods*/
#Scheduled(cron = "*/10 * * * * *")
public void processReport() {
InventoryReportProcessor reportProcessor = new InventoryReportProcessor();
Map<String, Long> skus = reportProcessor.process(new ByteArrayOutputStream());
}
}
My confusion comes from the fact that #Value in Service works fine but in #Component it returns null unless call in #PostConstruct. Also, if call #PostConstruct the value is still remains null in the rest of the class code.
I found similar Q&A and I did research in Srping docs but so far no single idea why it works this way and what can be a solution?
You need to Autowire the component to make your spring application aware of the component.
#Service
public class ReportService {
#Value("${mws.appVersion}")
private String appVersion;
/* Other initialization and public API methods*/
#Autowired
private ReportProcessor reportProcessor;
#Scheduled(cron = "*/10 * * * * *")
public void processReport() {
//InventoryReportProcessor reportProcessor = new InventoryReportProcessor();
Map<String, Long> skus = reportProcessor.process(new ByteArrayOutputStream());
}
}
Field injection is done after objects are constructed since obviously the container cannot set a property of something which doesn't exist.
at the time System.out.println(destinationFileToSave); triggers values are not being injected;
if you want to see it working try something like this
#Autowired
InventoryReportProcessor pross;
pross.process(ByteArrayOutputStream outputStream);
#PostConstruct works as it is being called after the object creation.
Spring will only parse #Value annotations on beans it knows. The code you use creates an instance of the class outside the scope of Spring and as such Spring will do nothing with it.
One thing you can do is to create the instance explictly or use Autowire:
#Autowired
private ReportProcessor reportProcessor;
tl:dr If you have configured your application context correctly then a #Value cannot be null as that will stop the correct startup of your application.
Change your Code from
#Value("${reportGenerator.path}")
private String destinationFileToSave;
to
#Value("${reportGenerator.path}")
public void setDestinationFileToSave(String destinationFileToSave) {
SendMessageController.destinationFileToSave = destinationFileToSave;
}

Dynamic Kafka Consumer with AOP

I have got few dynamic Kafka consumers (based upon the department id, etc..) and you can find the code below.
Basically, I wanted to log the time taken for each onMessage() method call and so I have created a #LogExecutionTime method level custom annotation and added it for onMessage() method .
But my logExecutionTime() of LogExecutionTimeAspect never gets called even though my onMessage() is being invoked whenever there is a message on to the topic and everything else works fine.
Could you please help on what am I missing LogExecutionTimeAspect class so that it starts working?
LogExecutionTime:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface LogExecutionTime {
}
LogExecutionTimeAspect class:
#Aspect
#Component
public class LogExecutionTimeAspect {
#Around("within(com.myproject..*) && #annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(" Time taken by Listener ::"+(endTime-startTime)+"ms");
return object;
}
}
DepartmentsMessageConsumer class:
#Component
public class DepartmentsMessageConsumer implements MessageListener {
#Value(value = "${spring.kafka.bootstrap-servers}" )
private String bootstrapAddress;
#PostConstruct
public void init() {
Map<String, Object> consumerProperties = new HashMap<>();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "DEPT_ID_HERE");
ContainerProperties containerProperties =
new ContainerProperties("com.myproj.depts.topic");
containerProperties.setMessageListener(this);
DefaultKafkaConsumerFactory<String, Greeting> consumerFactory =
new DefaultKafkaConsumerFactory<>(consumerProperties,
new StringDeserializer(),
new JsonDeserializer<>(Department.class));
ConcurrentMessageListenerContainer container =
new ConcurrentMessageListenerContainer<>(consumerFactory,
containerProperties);
container.start();
}
#Override
#LogExecutionTime
public void onMessage(Object message) {
ConsumerRecord record = (ConsumerRecord) message;
Department department = (Department)record.value();
System.out.println(" department :: "+department);
}
}
ApplicationLauncher class:
#SpringBootApplication
#EnableKafka
#EnableAspectJAutoProxy
#ComponentScan(basePackages = { "com.myproject" })
public class ApplicationLauncher extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationLauncher.class, args);
}
}
EDIT:
I have tried #EnableAspectJAutoProxy(exposeProxy=true), but did not work.
You should consider to turn on this option on the #EnableAspectJAutoProxy:
/**
* Indicate that the proxy should be exposed by the AOP framework as a {#code ThreadLocal}
* for retrieval via the {#link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {#code AopContext} access will work.
* #since 4.3.1
*/
boolean exposeProxy() default false;
On the other hand there is something like this, which is going to be better than AOP:
/**
* A plugin interface that allows you to intercept (and possibly mutate) records received by the consumer. A primary use-case
* is for third-party components to hook into the consumer applications for custom monitoring, logging, etc.
*
* <p>
* This class will get consumer config properties via <code>configure()</code> method, including clientId assigned
* by KafkaConsumer if not specified in the consumer config. The interceptor implementation needs to be aware that it will be
* sharing consumer config namespace with other interceptors and serializers, and ensure that there are no conflicts.
* <p>
* Exceptions thrown by ConsumerInterceptor methods will be caught, logged, but not propagated further. As a result, if
* the user configures the interceptor with the wrong key and value type parameters, the consumer will not throw an exception,
* just log the errors.
* <p>
* ConsumerInterceptor callbacks are called from the same thread that invokes {#link org.apache.kafka.clients.consumer.KafkaConsumer#poll(long)}.
* <p>
* Implement {#link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
*/
public interface ConsumerInterceptor<K, V> extends Configurable {
UPDATE
#EnableAspectJAutoProxy(exposeProxy=true) did not work and I know that I could use interceptor, but I wanted to make it working with AOP.
Then I suggest you to consider to separate a DepartmentsMessageConsumer and the ConcurrentMessageListenerContainer. I mean move that ConcurrentMessageListenerContainer into the separate #Configuration class. The ApplicationLauncher is a good candidate. Make it as a #Bean and dependent on your DepartmentsMessageConsumer for injection. The point is that you need to give an AOP a chance to instrument your DepartmentsMessageConsumer, but with the #PostConstruct, that's too early to instantiate and start consumption from Kafka.

Dynamic Queues on RabbitListener Annotation

I'd like to use queue names using a specific pattern, like project.{queue-name}.queue. And to keep this pattern solid, I wrote a helper class to generate this name from a simple identifier. So, foo would generate a queue called project.foo.queue. Simple.
But, the annotation RabbitListener demands a constant string and gives me an error using my helper class. How can I achieve this (or maybe another approach) using RabbitListener annotation?
#Component
public class FooListener {
// it doesn't work
#RabbitListener(queues = QueueName.for("foo"))
// it works
#RabbitListener(queues = "project.foo.queue")
void receive(final FooMessage message) {
// ...
}
}
To create and listen to a queue name constructed from a dynamic UUID, you could use random.uuid.
The problem is that this must be captured to a Java variable in only one place because a new random value would be generated each time the property is referenced.
The solution is to use Spring Expression Language (SpEL) to call a function that provides the configured value, something like:
#RabbitListener(queues = "#{configureAMQP.getControlQueueName()}")
void receive(final FooMessage message) {
// ...
}
Create the queue with something like this:
#Configuration
public class ConfigureAMQP {
#Value("${controlQueuePrefix}-${random.uuid}")
private String controlQueueName;
public String getControlQueueName() {
return controlQueueName;
}
#Bean
public Queue controlQueue() {
System.out.println("controlQueue(): controlQueueName=" + controlQueueName);
return new Queue(controlQueueName, true, true, true);
}
}
Notice that the necessary bean used in the SpEL was created implicitly based on the #Configuration class (with a slight alteration of the spelling ConfigureAMQP -> configureAMQP).
Declare a magic bean, in this case implicitly named queueName:
#Component
public class QueueName {
public String buildFor(String name) {
return "project."+name+".queue";
}
}
Access this using a "constant string" that will be evaluated at runtime:
#RabbitListener(queues = "#{queueName.buildFor(\"foo\")}")
If {queue-name} would came from yml file - it should work:
#RabbitListener(queues = "${queue-name}")
public void receiveMessage(FooMessage message) {
}
Spring will inject value from application.yml.

Schedule task for specific time from spring 4 web mvc

I am building a Spring 4 Rest API for a trade automation site.
An http request will contain some info along with a date-time. After inserting these info into database (using hibernate), I need to dynamically create a new cron job which will access these db info and do something. The cron job must be executed at the time specified above.
So there wont be a fixed cron expression, also the cron task must access my DAO layer annoted with #Repository.
Even after referring a lot of post in stack and other blog, which tells about #Scheduled, Spring-Quartz integration, I couldn't find out a solution for my specific need.
Java/Annotation configuration is preferred.
Please help.
Thanks
You can use Trigger and TaskScheduler interfaces. Example below. To cancel job in the future it will be needed to store ScheduledFuture instance.
#Configuration
public class AppConfiguration {
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
#Controller
public class TriggerService {
#Autowired
private TaskScheduler scheduler;
#Autowired
private DAOService db;
private ScheduledFuture job;
#GetMapping("/task1")
#ResponseBody
public void triggerMyTask(#RequestParam String cronExpression) {
Runnable runnable = new Runnable() {
#Override
public void run() {
log.info(new Date());
/**
* here You can do what You want with db
* using some DAOService
*/
db.count();
}
};
/**
* cancel current task if You need
*/
if(job != null) {
job.cancel(true);
}
CronTrigger trigger = new CronTrigger(cronExpression);
job = scheduler.schedule(runnable, trigger);
}
}
You can pass cron expression for example like that:
http://localhost:8080/task1?cronExpression=0/5%20*%20*%20*%20*%20*
I think you may use something like this: https://ha-jdbc.github.io/apidocs/net/sf/hajdbc/util/concurrent/cron/CronThreadPoolExecutor.html

Java EJB Modify Schedule Property Values At Runtime

I have a Singleton class in Java and I have a timer using the #Schedule annotation. I wish to change the property of the Schedule at runtime. Below is the code:
#Startup
#Singleton
public class Listener {
public void setProperty() {
Method[] methods = this.getClass().getDeclaredMethods();
Method method = methods[0];
Annotation[] annotations = method.getDeclaredAnnotations();
Annotation annotation = annotations[0];
if(annotation instanceof Schedule) {
Schedule schedule = (Schedule) annotation;
System.out.println(schedule.second());
}
}
#PostConstruct
public void runAtStartUp() {
setProperty();
}
#Schedule(second = "3")
public void run() {
// do something
}
}
I wish to change the value at runtime of Schedule second based on the information from a Property file. Is this actually possibe? The Property file contains the configuration information. I tried to do #Schedule(second = SOME_VARIABLE) where private static String SOME_VARIABLE = readFromConfigFile(); This does not work. It expects a constant meaning a final and I don't want to set final.
I also saw this post: Modifying annotation attribute value at runtime in java
It shows this is not possible to do.
Any ideas?
EDIT:
#Startup
#Singleton
public class Listener {
javax.annotation.#Resource // the issue is this
private javax.ejb.TimerService timerService;
private static String SOME_VARIABLE = null;
#PostConstruct
public void runAtStartUp() {
SOME_VARIABLE = readFromFile();
timerService.createTimer(new Date(), TimeUnit.SECONDS.toMillis(Long.parse(SOME_VARIABLE)), null);
}
#Timeout
public void check(Timer timer) {
// some code runs every SOME_VARIABLE as seconds
}
}
The issue is injecting using #Resource. How can this be fixed?
The Exception is shown below:
No EJBContainer provider available The following providers: org.glassfish.ejb.embedded.EJBContainerProviderImpl Returned null from createEJBContainer call
javax.ejb.EJBException
org.glassfish.ejb.embedded.EJBContainerProviderImpl
at javax.ejb.embeddable.EJBContainer.reportError(EJBContainer.java:186)
at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:121)
at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:78)
#BeforeClass
public void setUpClass() throws Exception {
Container container = EJBContainer.createEJBContainer();
}
This occurs during unit testing using the Embeddable EJB Container. Some of the Apache Maven code is located on this post: Java EJB JNDI Beans Lookup Failed
I think the solution you are looking for was discussed here.
TomasZ is right you should use programmatic timers with TimerService for the situations when you want dynamically change schedule in run time.
Maybe you could use the TimerService. I have written some code but on my Wildfly 8 it seems to run multiple times even if its a Singleton.
Documentation http://docs.oracle.com/javaee/6/tutorial/doc/bnboy.html
Hope this helps:
#javax.ejb.Singleton
#javax.ejb.Startup
public class VariableEjbTimer {
#javax.annotation.Resource
javax.ejb.TimerService timerService;
#javax.annotation.PostConstruct
public void runAtStartUp() {
createTimer(2000L);
}
private void createTimer(long millis) {
//timerService.createSingleActionTimer(millis, new javax.ejb.TimerConfig());
timerService.createTimer(millis, millis, null);
}
#javax.ejb.Timeout
public void run(javax.ejb.Timer timer) {
long timeout = readFromConfigFile();
System.out.println("Timeout in " + timeout);
createTimer(timeout);
}
private long readFromConfigFile() {
return new java.util.Random().nextInt(5) * 1000L;
}
}

Categories

Resources