Spring Boot - Alternative to Time.schedule? - java

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.

Related

How can I test programmatically created #Scheduled method?

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.

How can I manage (or pass as an argument) my config json to start the testing of my vertx application?

I have a small vertx application with an AppLauncher class that extend of VertxCommandLauncher and I set a appConfig.json with the typical config parameters :
public class AppLauncher extends VertxCommandLauncher implements VertxLifecycleHooks {
public static void main(String[] args) {
new AppLauncher().dispatch(args);
}
#Override
public void afterConfigParsed(JsonObject config) {
AppConfig.INSTANCE.setConfig(config);
}
To run my application in my IDE I put in edit configuration my main class (Applauncher.java) and the arguments :
run io.vertx.covid.verticle.MainVerticle -conf../vertx-application/src/main/resources/appConfig.json
This is my test class:
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext testContext) {
vertx.deployVerticle(BaseVerticle.class.getName(),testContext
.succeeding(id->testContext.completeNow()));
}
This is my BaseVerticle class that all my verticles extends from:
public abstract class BaseVerticle extends AbstractVerticle {
public static String CONTEXT_PATH = AppConfig.INSTANCE.getConfig().getString(Constants.CONTEXT_PATH);
}
And this is my AppConfig class :
public enum AppConfig {
INSTANCE;
private JsonObject config;
public JsonObject getConfig() {
return config;
}
public void setConfig(JsonObject config) {
this.config = config;
}
}
Everything works, but if I would like to test it in a separete way then I deploy my verticles but I have a Nullpointer in the CONTEXT_PATH (BaseVerticle class) because the config (suppose to be taken from appConfig.json) is null.
I haven't found a way to pass the arguments with my appConfig.json or should I call to the main method passing the arguments?
I like to do something that is similar to profiles in my vertx application.
If you set an environment variable with the key vertx-config-path before the vertx instance is initialized, you can control where vertx's config retriever (you might need to add vert-config to your gradle/maven dependencies) gets the configuration from.
In your launcher, you can do something like the following, which will give you the ability to add profile based config files to your resources folder conf/config-%s.json where %s is the profile name:
public class CustomLauncher extends Launcher {
public static final String ACTIVE_PROFILE_PROPERTY = "APP_ACTIVE_PROFILE";
private static final CLI cli = CLI.create("main")
.addOption(new Option()
.setShortName("p")
.setLongName("profile")
);
public static void main(String[] args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().dispatch(args);
}
public static void executeCommand(String cmd, String... args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().execute(cmd, args);
}
public static void initDefaults(List<String> args) {
System.setProperty(LoggerFactory.LOGGER_DELEGATE_FACTORY_CLASS_NAME, SLF4JLogDelegateFactory.class.getName());
CommandLine parse = cli.parse(args);
String profile = parse.getOptionValue("p");
if (profile != null && !profile.isEmpty()) {
System.setProperty(ACTIVE_PROFILE_PROPERTY, profile);
System.setProperty("vertx-config-path", String.format("conf/config-%s.json", profile));
}
}
}
Then in your test, instead of relaying on vertx test extension to inject vertx for you, you can initialize it by yourself and control the profile (aka which config file to load) like the following:
private static Vertx vertx;
#BeforeAll
public static void deployVerticles(VertxTestContext testContext) {
CustomLauncher.initDefaults(Arrays.asList("--profile", "test"))
vertx = Vertx.vertx();
ConfigRetriever.create(vertx).getConfig(asyncResult -> {
if (asyncResult.succeeded()) {
JsonObject config = asyncResult.result();
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setConfig(config);
vertx.deployVerticle(BaseVerticle.class.getName(), deploymentOptions);
} else {
// handle failure
}
});
}
Then when you run your application, instead of providing -conf, you can use -p or --profile
I also highly recommend to get familiar with vertx-config as you can also get env variables, k8s config maps, and much more.
EDIT: I also highly recommend to move to Kotlin if possible, makes the async-code much easier to handle in an imperative way (with Coroutines). It's very hard to deal with libraries like Vert.x in Java compared to languages like Kotlin.
I solved my problem creating a verticle with the config stuffs (vertx-config documentation), here is my verticle config class:
public class ConfigVerticle extends AbstractVerticle {
protected static Logger logger = LoggerFactory.getLogger(ConfigVerticle.class);
public static JsonObject config;
#Override
public void start() throws Exception {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setOptional(true)
.setConfig(new JsonObject().put("path", "conf/appConfig.json"));
ConfigStoreOptions sysPropsStore = new ConfigStoreOptions().setType("sys");
ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore).addStore(sysPropsStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig(ar -> {
if (ar.failed()) {
logger.info("Failed to retrieve config from appConfig.json");
} else {
config = ar.result();
vertx.deployVerticle(MainVerticle.class.getName(), new DeploymentOptions().setConfig(config));
}
});
}
}
And my MainVerticle.class I pass the new configuration like this:
public class MainVerticle extends AbstractVerticle {
#Override
public void start(){
vertx.deployVerticle(BackendVerticle.class.getName(), new DeploymentOptions().setConfig(config()));
}
}
Then, my simpleTests :
#ExtendWith(VertxExtension.class)
public class BaseCovidTest {
protected WebClient webClient;
#BeforeEach
void initWebClient(Vertx vertx){
webClient = WebClient.create(vertx);
}
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext vertxTestContext) {
vertx.deployVerticle(ConfigVerticle.class.getName() ,vertxTestContext
.succeeding(id-> {
try {
vertxTestContext.awaitCompletion(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
vertxTestContext.completeNow();
}));
}
}
And everything works, thanks #Tom that inspired me to fix it!

How to add custom MeterRegisty for Spring Boot 2

I am currently exporting Actuator metrics for my Spring Boot Webflux project to DataDog with 10 seconds interval. I would like to add another exporter for one of our internal system that is not in the list of supported backends. Looking at the implementation from DataDogMeterRegistry I came up with the following.
public interface ExternalConfig extends StepRegistryConfig {
ExternalConfig DEFAULT = k -> null;
#Override
default String prefix() {
return "vwexternal";
}
}
#Slf4j
public class ExternalMeterRegistry extends StepMeterRegistry {
public ExternalMeterRegistry() {
this(ExternalConfig.DEFAULT, Clock.SYSTEM);
}
public ExternalMeterRegistry(StepRegistryConfig config, Clock clock) {
super(config, clock);
}
#Override
protected void publish() {
log.info("HERE");
}
#Override
protected TimeUnit getBaseTimeUnit() {
return TimeUnit.MILLISECONDS;
}
}
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
Metrics.addRegistry(new ExternalMeterRegistry());
}
}
However this is not working since no logs are printed.
My question is how can I add and implement another MeterRegistry for Spring Boot Micrometer?
You need to start the publishing. Compare with the LoggingMeterRegistry
In your constructor something like:
start(new NamedThreadFactory("vw-metrics-publisher"))

What is a common pattern for scheduled event handling in Spring?

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

Tapestry: Inject at runtime

again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}

Categories