In my application I need to add scheduled event checking and handling. When some business logic happened I need to create a posponed trigger, which should fire some actions through a particular time gap. For example:
If user posted photo, he should be notified if there is no likes under it within three days.
I feel that it should be a common pattern for such activities, relied on Spring framework features.
In your main config you need some like :
#Configuration
#EnableScheduling
public class HelloWorldConfig { ..}
Then in you bean where you want to schedule something :
#Scheduled(fixedRate=1000)
public void reload() { ..}
See http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html
Make sure you include the #EnableScheduling or equivalent if you're using XML config.
#Component
public class DemoExpirationEvent implements Runnable {
#Resource(name = "demoPhotoService")
private DemoExpirationService demoExpirationService;
#Resource(name = "demoExpirationTaskScheduler")
private TaskScheduler taskScheduler;
private Long id;
#Override
public void run() {
demoExpirationService.expiration(id);
}
public void schedule(Long id, Date dateToExpire){
this.id = id;
taskScheduler.schedule(this, dateToExpire);
}
}
#Service("demoPhotoService")
public class DemoPhotoServiceImpl implements DemoExpirationService, DemoPhotoService {
#Override
public void expiration(Long id) {
DemoPhoto photo = getPhoto(id);
photo.setExpirationDate(null);
savePhoto(photo);
notifyAuthorOfPhoto(id);
}
#Override
public void getPhoto(long id){
//some implementation
}
#Override
public void savePhoto(DemoPhoto photo){
//some implementation
}
#Override
public void notifyAuthorOfPhoto(long id){
//some implementation
}
}
public class DemoAddedPhotoActivity {
#Resource(name = "demoExpirationEvent")
private DemoExpirationEvent demoExpirationEvent;
#Resource(name = "demoPhotoService")
private DemoPhotoService demoPhotoService;
public void execute(long id) throws Exception {
DemoPhoto photo = demoPhotoService.getPhoto(id);
Date expirationDate = new Date(System.currentTimeMillis() + 30000000000L);
photo.setExpirationDate(expirationDate);
demoPhotoService.savePhoto(photo);
demoExpirationEvent.schedule(id, expirationDate);
}
}
register task scheduler in your applicationContext.xml
<beans xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.1.xsd">
<task:scheduler id="demoExpirationTaskScheduler" pool-size="3"/>
And rest of beans too and call activity on adding photo (it can be controller method with execute like method
Related
I've seen lots of guides on how to test a #Scheduled bean. But what if I create it programmatically?
I am creating a scheduled job( goodBoy.bark() ) that will run every 5 seconds. This is pretty much the equivalent of #Scheduler annotation.
#EnableScheduling
public class DogScheduler implements SchedulingConfigurer {
private final Dog goodBoy;
#Autowired
public DogScheduler(Dog goodBoy){
this.goodBoy= goodBoy;
}
#Bean
public Executor taskExecutor() {
return Executors.newSingleThreadScheduledExecutor();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
goodBoy::bark,
context -> {
Optional<Date> lastCompletionTime =
Optional.ofNullable(context.lastCompletionTime());
Instant nextExecutionTime =
lastCompletionTime.orElseGet(Date::new).toInstant()
.plusSeconds(5);
return Date.from(nextExecutionTime);
}
);
}
}
#Component
public class Dog {
private Food meat;
#Autowired
public DomainRefreshScheduler(Food meat){
this.meat= meat;
}
public void bark(){
log.warn("woof!");
}
}
How can I test goodBoy.bark() method? I would like for example, to start the application, wait for 10 seconds and then verify that goodBoy.bark() was called 2 times.
I searched for a while but have no clue what I am missing.
I want to implement the observer pattern in Spring, to update documents in MongoDB which will create a notification in the front-end.
I implemented a Service class for the notifications (here are more than one, I show you just two of them), using #EventListener annotation
#Service
#RequiredArgsConstructor
public class NotificationService {
#EventListener ({ContextStartedEvent.class})
public void updateNotificationForIdea(String ideaId) {
//some logic inside the Service classes
}
}
#EventListener ({ContextStartedEvent.class})
public void createNotificationForNewComment(String ideaId, String creatorId) {
//some logic inside the Service classes
}
}
and I tried to implement the service, from where I want to send the notification
#Service
#RequiredArgsConstructor
public class CommentService {
private final ApplicationEventPublisher eventPublisher;
public CommentServiceResult createComment(Comment comment, String creatorId) {
// some logic
eventPublisher.publishEvent(); //here is where I miss something
return CommentServiceResult.builder()
.comment(createdComment)
.resultState(state)
.build();
}
I read that I need something like an "EventHelperClass" and tried to build this:
public class NotificationEvents extends ApplicationEvent{
public NotificationEvents(Object source) {
super(source);
}
private void updateNotificationForIdea(String ideaId){
}
private void createNotificationForNewComment(String ideaId, String creatorId) {
}
}
but I really don't know how to get the notifications from the NotificationsService in here (because they are void and are not a simple String message), to start the event from within CommentService...
I shortened the dependencies from the services and removed the internal logik for readability...
eventPublisher.publishEvent(...)
fires an event, you'll need to pass an event object as parameter, e.g.
public class CustomSpringEvent extends ApplicationEvent {
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
Methods annotated with #EventListener ({CustomSpringEvent.class}) will catch it.
In your case:
CommentService fires an CommentEvent (fields: Comment comment, String creatorId).
Any Bean with a method
#EventListener ({CommentEvent.class})
void onCommentEvent(CommentEvent event) {
// ....
}
will catch it and get the event data.
https://www.baeldung.com/spring-events
I have this scheduler which is used to check for new data into DB table:
#Service
public class ReloadCache {
#Scheduled(fixedDelay = 1000)
public void reload() {
...... do something
}
}
......
#Service
public class LogicalClient {
final Map<String, Map<String, String>> map;
#PostConstruct
public void initializeBalances() {
............ map = new HashMap.......
}
#KafkaListener(......")
public void handle(....) {
.......read map here
}
}
Note that these two services are located in separate Java classes and packages.
When schedule runs and change is detected how how I can call again Java method initializeBalances annotated with #PostConstruct in order to generate again the map structure?
Inject your LogicalClient in your ReloadCache class and call that function like this:
#Service
public class ReloadCache {
private final LogicalClient logicalClient;
public ReloadCache(LogicalClient client) // Injection through constructor.
{
this.logicalClient = client;
}
#Scheduled(fixedDelay = 1000)
public void reload() {
...... do something
client.initializeBalances()
}
}
Both your classes are annotated with the #Service. So they are both Spring beans that can be injected wherever you find it suitable (as long as the receiving class is a bean itself).
Let's say I have a class that is loading some events:
#Singleton
class EventStarter {
#Value("${very.important.key}")
private String key;
public EventStarter(MyEvents events) {
this.events = events;
}
private final MyEvents events;
#EventListener
void onApplicationEvent(ServerStartupEvent event) throws IOException {
events.register(key);
}
}
I have heard that I can refactor this code to use #Context annotation instead of using an #EventListener. I have tried to do this, but I have failed miserably.
I have another class using #Context annotation, which looks like this:
#Context
#Singleton
#Requires(property = "JASYPT_ENCRYPTOR_PASSWORD")
public class JasyptDecryptor implements BeanInitializedEventListener<Environment> {
public JasyptConfigurationPropertiesDecryptor(DefaultEnvironment environment, #Value("${JASYPT_ENCRYPTOR_PASSWORD}") String encryptedPassword) {
LOGGER.info("JasyptBootstrapDecryptor started");
processConfigurationProperties(environment, encryptedPassword);
}
private void processConfigurationProperties(DefaultEnvironment environment, String encryptedPassword) {
// unrelated code here
}
#Override
public Environment onInitialized(BeanInitializingEvent<Environment> event) {
return event.getBean();
}
}
this class is implementing BeanInitializedEventListener<Environment>. Should I implement this somehow in my EventStarter class? As I understand, it should not take Environment as a type, but what should it be? ServerStartupEvent?
At some point I have reached something like this:
#Context
#Singleton
#Requires(property = "${very.important.key}")
class EventStarter implements BeanInitializedEventListener<ServerStartupEvent> {
public EventStarter(MyEvents events) {
this.events = events;
startEvents()
}
private final MyEvents events;
private void startEvents() throws IOException {
events.register(key);
}
#Override
public Environment onInitialized(BeanInitializingEvent<ServerStartupEvent> event) {
return event.getBean();
}
}
It seemed to work... partially - I got an exception just very little telling about "token", so it seems that this ${very.important.key} was, as I presume, empty or null, so it wasn't read from application.yml.
What am I doing wrong here? Is there a way to achieve this?
I want to do something like javascript's setInterval(function, interval)/setTimeout(function, timeout) in Spring Boot.
I found the #Scheduled annotation that has the fixedRate argument, but as an annotation I cannot change the rate dynamically (Or can I?)
For now I am using java.util.Timer, but I would rather use Spring. Is there a way?
Can I get a Scheduler instance and work with it dynamically?
thanks!
You may use a Trigger which lets you dynamically control the next execution. You need to implement SchedulingConfigurer, another answer covers exactly this:
Scheduling a job with Spring programmatically (with fixedRate set dynamically)
EDIT to answer comments:
nextExecutionTime is called on and on and on... The next time the task (and nextExecutionTime) is called is defined by this:
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND, numberOfMillisecondsBeforeCallingTheTask);
All you need to do is have this numberOfMillisecondsBeforeCallingTheTask value changed.
Example:
#RestController
public class MyController {
public static int triggerDelay = 1000;
#RequestMapping("/changetrigger/{val}")
public void test(#PathVariable int val){
this.triggerDelay = val;
}
}
#SpringBootApplication
#EnableScheduling
public class Launcher implements SchedulingConfigurer{
public static void main(String[] args){
new SpringApplicationBuilder() //
.sources(Launcher.class)//
.run(args);
}
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
;
taskRegistrar.addTriggerTask(new TriggerTask(new Runnable() {
#Override
public void run() {
System.out.println("blah");
System.out.println(System.currentTimeMillis());
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Calendar nextExecutionTime = new GregorianCalendar();
nextExecutionTime.setTime(new Date());
nextExecutionTime.add(Calendar.MILLISECOND, MyController.triggerDelay);
System.out.println(System.currentTimeMillis());
return nextExecutionTime.getTime();
}}));
}
}
Notice how the dynamic value MyController.triggerDelay is used for the next execution. So if you change the number, the next execution time will be changed. You'll see if you put a breakpoint inside nextExecutionTime.
You can use #Scheduled(fixedRateString = "${spring.boot.schedule.rate}") for your case, where the spring.boot.schedule.rate is the external properties in application.properties
spring.boot.schedule.rate=5000
Misunderstand the question, above is just the externalize the properties.
For the dynamic solution, maybe this should be work, using the spEL in the annonation:
#Service
public class ScheduledService {
#Autowired
private FixRateProperty fixRateProperty;
#Scheduled(fixedRateString = "#{fixRateProperty.fixRate}")
private void reportCurrentTime() {
System.out.println(new Date());;
}
}
This is the FixRateProperty
#Component
public class FixRateProperty {
private Long fixRate = 500L;
public Long getFixRate() {
return fixRate;
}
public void setFixRate(Long fixRate) {
this.fixRate = fixRate;
}
}
so you can externalize the rate in the properties or set the fixRate somewhere.
Found a solution that works for my case.
In Main.java:
#SpringBootApplication
#ConfigurationProperties
#EnableScheduling
public class Main {
#Bean
ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
In Service.java (Called from a rest controller):
#Service
public class Service {
private static final Logger log = LoggerFactory.getLogger(Service.class);
private final TaskScheduler scheduler;
#Autowired
public Service(TaskScheduler scheduler) {
this.scheduler = scheduler;
}
public void startTask(int inteval) {
scheduler.schedule(() -> log.info("Doing work"), triggerContext -> {
if (some_condition) {
ZonedDateTime now = ZonedDateTime.now();
return Date.from(now.plusSeconds(interval).toInstant());
} else {
// Stop the execution
return null;
}
});
}
}
This solution works, but I'm not sure it is the correct way.
You are welcome to comment below, and I might change the solution if I get a suggestion I find helpful.