I'm trying to run a method in Spring with ScheduledTasks, so I have the following class:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.format.DateTimeFormatter;
#Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
public void scheduleTaskWithFixedRate() {
}
public void scheduleTaskWithFixedDelay() {
}
public void scheduleTaskWithInitialDelay() {
}
public void scheduleTaskWithCronExpression() {
}
}
And the following method in a different class
#Scheduled(fixedRate = 10 * 1000) //10 seconds
public void taskThatRunsPeridically() {
logger.info("Scheduled task method has been called ");
}
But the method never runs, I've noticed thought that if I move the method to the Spring Boot Application class (the class that hosts main)
Why is this happening? How I can get schedule methods to run in wherever class that I add them?
You have to add the #EnableScheduling annotation in one of your Spring configuration classes or above the other class that contains your method, for example:
#Component
#EnableScheduling
public MySchdeduleClass {
#Scheduled(fixedRate = 10 * 1000) //10 seconds
public void taskThatRunsPeridically() {
logger.info("Scheduled task method has been called ");
}
}
Related
I am trying to implement a singleton pattern with a caching feature. At first MySingleton was only a POJO and things were simple enough, but then I needed to add a new feature, which also required autowiring a bean. (MyComponent is really an interface to a data repository)
I put the #Component annotation on MySingleton to trigger the autowiring (even though it is always called a static way) and created a private constructor to pass the MyComponent reference to an object created by new. This code seems to work, although I do not fully understand why.
My question: I feel like I'm doing it wrong, but am I?
(would you approve this pull request to your code base?)
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
public class MySingletonTest {
#Test
public void test() {
assertNotNull(MySingleton.getInstance().getMyComponent());
}
}
// ----------------------------------------------------------------------------- //
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class MySingleton {
private static final long CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
private static final AtomicReference<MySingleton> INSTANCE = new AtomicReference<MySingleton>();
private final Calendar timestamp; // NOTE: this is NOT static!
#Autowired
private static MyComponent myComponent;
private MySingleton(MyComponent myComponent) {
this.timestamp = Calendar.getInstance();
MySingleton.myComponent = myComponent; // I do not understand why this line is needed
}
private boolean isTimeout() {
return Calendar.getInstance().getTimeInMillis() - timestamp.getTimeInMillis() > CACHE_TIMEOUT;
}
public static synchronized MySingleton getInstance() {
if ( INSTANCE.get() == null || INSTANCE.get().isTimeout() ) {
INSTANCE.set(new MySingleton(myComponent));
}
return INSTANCE.get();
}
public MyComponent getMyComponent() {
return myComponent;
}
}
// ----------------------------------------------------------------------------- //
import org.springframework.stereotype.Component;
#Component
public class MyComponent {
}
I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.
I created a simple aspect in Spring using Spring Boot 2.1.6.RELEASE.
It basically logs the total time spent on a method.
#Aspect
#Component
public class TimeLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(TimeLoggerAspect.class);
#Around("#annotation(demo.TimeLogger)")
public Object methodTimeLogger(ProceedingJoinPoint joinPoint)
throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.info("Method " + joinPoint.getSignature() + ": " + totalTime + "ms");
return proceed;
}
}
the aspect is triggered by a TimeLogger annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface TimeLogger {
}
and is used in a component like this
#Component
public class DemoComponent {
#TimeLogger
public void sayHello() {
System.out.println("hello");
}
}
A spring boot demo application will invoke sayHello via the run method of the CommandLineRunner interface.
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
#Autowired
private DemoComponent demoComponent;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
demoComponent.sayHello();
}
}
For completeness, I add my modifications in build.gradle: adding libraries for aop, spring test and jupiter (junit).
compile("org.springframework.boot:spring-boot-starter-aop")
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("org.junit.jupiter:junit-jupiter-api")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
Running the application will output (trimmed for readability)
hello
... TimeLoggerAspect : Method void demo.DemoComponent.sayHello(): 4ms
So far, so good. Now I create a test based on #SpringBootTest annotation and jupiter.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {DemoComponent.class, TimeLoggerAspect.class})
public class DemoComponentFailTest {
#Autowired
private DemoComponent demoComponent;
#Test
public void shouldLogMethodTiming() {
demoComponent.sayHello();
}
}
and here I get the output
hello
No output from the TimeLoggerAspect, since it seems it is not being triggered.
Is something missing to trigger the aspect in the test? Or are there other ways of testing the aspect in spring boot?
I had similar problem. My Aspect is listening on controller methods. To get it activated, importing the AnnotationAwareAspectJAutoProxyCreator made the trick:
#RunWith(SpringRunner.class)
#Import(AnnotationAwareAspectJAutoProxyCreator.class) // activate aspect
#WebMvcTest(MyController.class)
public class MyControllerTest {
...
}
You have to put #EnableAspectJAutoProxy with your file #Configuration that declares the bean with #Aspect.
#Aspect
#Configuration
#EnableAspectJAutoProxy
public class TimeLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(TimeLoggerAspect.class);
#Around("#annotation(demo.TimeLogger)")
public Object methodTimeLogger(ProceedingJoinPoint joinPoint)
throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.info("Method " + joinPoint.getSignature() + ": " + totalTime + "ms");
return proceed;
}
}
I think that will do the work.
You need to start an #SpringBootApplication. However, it does not have to be the one you use to start your app in production. It can be a special one for this test only and can be in your test sources root not your src.
#SpringBootApplication
#ComponentScan(basePackageClasses = {DemoComponent.class, TimeLoggerAspect.class})
public class SpringBootTestMain {
public static void main(String[] args) {
SpringApplication.run(SpringBootTestMain.class, args);
}
}
Then in your test this is the only class you need to list.
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = SpringBootTestMain.class)
public class DemoComponentFailTest {
Another solution that seems to work is adding AnnotationAwareAspectJAutoProxyCreator in classes of #SpringBootTest, although I am not quite certain why.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = { DemoComponent.class,
TimeLoggerAspect.class,
AnnotationAwareAspectJAutoProxyCreator.class })
public class DemoComponentFailTest {
#Autowired
private DemoComponent demoComponent;
#Test
public void shouldLogMethodTiming() {
demoComponent.sayHello();
}
}
When I had to test an aspect, I used the approach below.
#SpringBootTest
#ContextConfiguration(classes = {MyAspectImpl.class, MyAspectTest.TestConfiguration.class})
#EnableAspectJAutoProxy
public class MyAspectTest {
#org.springframework.boot.test.context.TestConfiguration
static class TestConfiguration {
#Bean
public MyAspectTestClass myAspectTestClass() {
return new MyAspectTestClass();
}
}
#Autowired
private MyAspectTestClass target;
#Test
public void testCorrectlySetsPoolNameUsingMethodParameter() {
target.testMethod();
}
#NoArgsConstructor
private static class MyAspectTestClass {
#MyAspect
public void testMethod() {
//Add some logic here
}
}
}
I have this simple example of Spring Scheduler:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class AppScheduler {
#Scheduled(fixedRate = 10000)
public void myScheduler() {
System.out.println("Test print");
}
}
Is there a way to trigger execution in the current moment let's say from web page?
Just make a dump controller to call myScheduler method:
#Controller
public class DumpController {
#Autowired
private AppScheduler scheduler;
#RequestMapping("/ping")
public void ping() {
scheduler.myScheduler();
}
}
I am building a Springboot application and I want to turn on a scheduled method from the front-end. (as in I want the scheduler to run only after the method is called from the front-end)
This scheduled method will then call a web-service with the given parameter and keep on running until a specific response ("Success") is received.
Once the specific response is received, I want the scheduled method to stop running until it is called again from the front end.
I am not sure how to start and stop the execution of the scheduled method.
I have this currently:
#Component
public class ScheduledTasks {
private static final Logger LOG = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
#Scheduled(fixedRate = 5000)
public void waitForSuccess(String componentName) {
LOG.info("Running at: " + dateFormat.format(new Date()));
String response = MyWebService.checkStatus(componentName);
if ("success".equalsIgnoreCase(response)) {
LOG.info("success");
//Stop scheduling this method
} else {
LOG.info("keep waiting");
}
}
}
Here is my controller through which the scheduled method is to be turned on:
#Controller
public class MainController {
#GetMapping(/start/{componentName})
public #ResponseBody String startExecution(#PathVariable String componentName) {
//do some other stuff
//start scheduling the scheduled method with the parameter 'componentName'
System.out.println("Waiting for response");
}
}
Is my approach correct? How can I achieve this functionality using springboot and schedulers?
Here is the full example of start/stop API for a scheduled method in Spring Boot. You can use such APIs:
http:localhost:8080/start - for starting scheduled method with fixed rate 5000 ms
http:localhost:8080/stop - for stopping scheduled method
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.time.Instant;
import java.util.concurrent.ScheduledFuture;
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class TaskSchedulingApplication {
public static void main(String[] args) {
SpringApplication.run(TaskSchedulingApplication.class, args);
}
#Bean
TaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
#Controller
class ScheduleController {
public static final long FIXED_RATE = 5000;
#Autowired
TaskScheduler taskScheduler;
ScheduledFuture<?> scheduledFuture;
#RequestMapping("start")
ResponseEntity<Void> start() {
scheduledFuture = taskScheduler.scheduleAtFixedRate(printHour(), FIXED_RATE);
return new ResponseEntity<Void>(HttpStatus.OK);
}
#RequestMapping("stop")
ResponseEntity<Void> stop() {
scheduledFuture.cancel(false);
return new ResponseEntity<Void>(HttpStatus.OK);
}
private Runnable printHour() {
return () -> System.out.println("Hello " + Instant.now().toEpochMilli());
}
}
Start/stop API for a scheduled method in Spring Boot.
#Component
public class ScheduledTasks {
private Logger logger = Logger.getLogger(ScheduledTasks.class);
#Value("${jobs.schedule.istime}")
private boolean imagesPurgeJobEnable;
#Override
#Transactional(readOnly=true)
#Scheduled(cron = "${jobs.schedule.time}")
public void execute() {
//Do something
//can use DAO or other autowired beans here
if(imagesPurgeJobEnable){
// Do your conditional job here...
}
}
}