Spring Schedule expression configure externally - spring

I use a Spring Schedule in my application. Now I will configure the cron expression externally. This one at the bottom does not work. Has anyone an idee how to do this?
#EnableConfigurationProperties
#ConfigurationProperties(locations = "classpath:application.properties", ignoreUnknownFields = true, prefix = "myProject.prefix")
public class MyClass{
#Scheduled(cron = "${myProperty}")
public void myMethod() {

Have you activated scheduling in your configuration?
#Configuration
#EnableScheduling
public class AppConfig {
[...]
}

Related

How inject DataSource(HikariCP) in Auto Configuration class?

I'm trying to use Spring Boot 2 + Spring Data + Custom Auto Configuration Classes but for some reason can't inject DataSource(provided by HikariCP) in the third class.
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class InitialAutoConfiguration {
//Beans to load in theory first.
}
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
#AutoConfigureAfter(InitialAutoConfiguration.class)
#EntityScan(basePackageClasses = Asset.class)
#EnableJpaRepositories(basePackageClasses = AssetRepository.class,
repositoryBaseClass = BaseRepositoryImpl.class,
repositoryFactoryBeanClass = ExtendedJpaRepositoryFactoryBean.class)
public class JpaAutoConfiguration { //Load Jpa Classes
}
#Configuration
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#AutoConfigureAfter(JpaAutoConfiguration.class)
#ComponentScan(basePackages = {"com.xxx"})
public class ServiceConfiguration {
#Inject
private DataSource dataSource; //Datasource is null
#Bean
public DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
DbPropertySourcesPlaceholderConfigurer placeholderConfigurer = new DbPropertySourcesPlaceholderConfigurer(dataSource);
placeholderConfigurer.setPlaceholderPrefix("%{");
placeholderConfigurer.setPlaceholderSuffix("}");
return placeholderConfigurer;
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xetec.autoconfigure.InitialAutoConfiguration,\
com.xetec.autoconfigure.JpaAutoConfiguration,\
com.xetec.autoconfigure.ServiceConfiguration
Looks like my classes are loading before the Spring Data Classes(DataSourceAutoConfiguration.Hikari).
Is there one way to first load the Spring Boot Starters Classes then after my custom ones please?
Thanks
I am no sure if the following change can solve your issue, but i meet with the similar problem
add static in your method
#Bean
public static DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
....
}
Your use of highest and lowest precedence is the wrong way round. Your ServiceConfiguration is ordered with highest precedence which means that it will be evaluated first.
Rather than using absolute ordering, I would use #AutoConfigureAfter(DataSourceAutoConfiguration.class)

Mocking a #Bean defined in a #ConfigurationProperties Class

I have a #ConfigurationProperties class like this:
#ConfigurationProperties(prefix = "myprops", ignoreUnknownFields = false)
#Configuration
public class MyProperties {
private Long mySchedulerRate;
#Bean
public Long mySchedulerRate() {
return this.mySchedulerRate;
}
}
I'm registering it as a bean so I can refer to it in an annotation for a Spring scheduler:
#Scheduled(fixedRateString = "#{#mySchedulerRate}")
public void runScheduledUpdate() {
...
{
However, I now want to write a unit test where I want to be able to set a different value for the bean 'mySchedulerRate'. Mocking/Spying on the #ConfigurationProperties class doesnt seem to work since the scheduler gets set up before the stubbing has been set to return my desired value.
What is the easiest way to achieve what I am trying to do?
Managed to fix this now. I was running a #SpringBootTest and I realise you can override properties here within the annotation for a particular test class.
This worked for me:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class, properties = "myprops.my-scheduler-rate=1000")
public class MyTest {
So no need to try and override the bean, I was overcomplicating this far too much.

Disable #EnableScheduling on Spring Tests

When I run my unit tests, it invokes my scheduled tasks. I want to prevent this behaviour, which is caused by the fact that I have #EnableScheduling on my main app configuration.
How can I disable this on my unit tests?
I have come across this question/answer which suggests setting up profiles?
Not sure how I would go about that? or if its an overkill? I was thinking of having a separate AppConfiguration for my unit tests but it feels like im repeating code twice when I do that?
#Configuration
#EnableJpaRepositories(AppConfiguration.DAO_PACKAGE)
#EnableTransactionManagement
#EnableScheduling
#ComponentScan({AppConfiguration.SERVICE_PACKAGE,
AppConfiguration.DAO_PACKAGE,
AppConfiguration.CLIENT_PACKAGE,
AppConfiguration.SCHEDULE_PACKAGE})
public class AppConfiguration {
static final String MAIN_PACKAGE = "com.etc.app-name";
static final String DAO_PACKAGE = "com.etc.app-name.dao";
private static final String ENTITIES_PACKAGE = "com.etc.app-name.entity";
static final String SERVICE_PACKAGE = "com.etc.app-name.service";
static final String CLIENT_PACKAGE = "com.etc.app-name.client";
static final String SCHEDULE_PACKAGE = "com.etc.app-name.scheduling";
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
// stripped code for question readability
}
// more app config code below etc
}
Unit test example.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={AppConfiguration.class})
#Transactional
#TransactionConfiguration(defaultRollback = true)
#WebAppConfiguration
public class ExampleDaoTest {
#Autowired
ExampleDao exampleDao;
#Test
public void testExampleDao() {
List<Example> items = exampleDao.findAll();
Assert.assertTrue(items.size()>0);
}
}
If you don't want to use profiles, you can add flag that will enable/disable scheduling for the application
In your AppConfiguration add this
#ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
#Configuration
#EnableScheduling
public static class SchedulingConfiguration {
}
and in your test just add this annotation to disable scheduling
#TestPropertySource(properties = "app.scheduling.enable=false")
I just parameterized my #Scheduled annotation with configurable delay times:
#Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")
In my test application.yaml:
timing:
updateData: 60000
initialDelay: 10000000000
And main application.yaml:
timing:
updateData: 60000
initialDelay: 1
It's not turning it off but creating such a long delay, the tests will be long over before it runs. Not the most elegant solution but definitely one of the easiest I've found.
One more solution without any change in production code, using the #MockBean.
#RunWith(SpringRunner.class)
#SpringBootTest
#MockBean(MyScheduledClass.class)
public class MyTest {
Which will eventually replace active scheduled job or create a mocked one.
From the documentation
Mocks can be registered by type or by {#link #name() bean name}. Any existing single
bean of the same type defined in the context will be replaced by the mock, if no
existing bean is defined a new one will be added.
An alternative would be to unregister the bean post processor that schedules the events. This can be done by simply putting the following class on the classpath of your tests:
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
((DefaultListableBeanFactory)beanFactory).removeBeanDefinition(beanName);
}
}
}
While this is quite simple and seems to do the job, beware that I did not test this very much or check for possible implications of removing a defined bean from the registry or making sure that ordering of PostProcessors won't be an issue...
With Spring Boot and cron expression you can enable or disable scheduling.
For example you can define an test application.yml and set
scheduler:
cron-expr: '-'
See also disable scheduling with '-'.
In your scheduler class you can pass the expression.
#Scheduled(cron = "${scheduler.cron-expr}")
Discovered that adding
app.scheduling.enable=false
in test application.properties along with
#ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
#EnableScheduling
to scheduling configuration class annotations like in Marko Vranjkovic's answer works for all tests without need to annotate each of them!
In each Test you define which spring configuration should be used, currently you have:
#ContextConfiguration(classes={AppConfiguration.class})
Common practice is to define separate spring configuration for your normal application and for your tests.
AppConfiguration.java
TestConfiguration.java
Then in your test you simply refference TestConfiguration instead of your current AppConfiguration using #ContextConfiguration(classes={TestConfiguration.class})
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={TestConfiguration.class})
#Transactional
#TransactionConfiguration(defaultRollback = true)
#WebAppConfiguration
public class ExampleDaoTest
This way you can configure any setting for your tests differently than in production code. You can for example use in-memory database for your tests instead of regular one and much more.
I was able to solve this problem by creating a method that removes the scheduled tasks during unit tests.
Here is an example:
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.context.ApplicationContext;
public static void removeScheduledTasks(ScheduledAnnotationBeanPostProcessor postProcessor, ApplicationContext appContext) {
postProcessor.setApplicationContext(appContext);
postProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
}
}
Use example:
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.Utils;
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestRemoveScheduller {
#Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
#Autowired
private ApplicationContext appContext;
#Before
public void init(){
//Some init variables
//Remove scheduled tasks method
Utils.removeScheduledTasks(postProcessor, appContext);
}
//Some test methods
}
Hope this helps.
I was looking to do this in a normal class (not a unit test). I have my main Spring Boot application but needed a small utility class to do some bulk data cleanup. I wanted to use the full application context of my main app but turn off any scheduled tasks. The best solution for me was similar to Gladson Bruno:
scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
Another advantage of this approach is you can get a list of all scheduled tasks, and you could add logic to cancel some tasks but not others.
create TestTaskScheduler Bean in test class
public class TestTaskScheduler implements TaskScheduler {
private static final NullScheduledFuture NULL_SCHEDULED_FUTURE = new NullScheduledFuture();
#Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
return NULL_SCHEDULED_FUTURE;
}
private static class NullScheduledFuture implements ScheduledFuture {
#Override
public long getDelay(TimeUnit unit) {
return 0;
}
#Override
public int compareTo(Delayed o) {
return 0;
}
#Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
#Override
public boolean isCancelled() {
return false;
}
#Override
public boolean isDone() {
return false;
}
#Override
public Object get() throws InterruptedException, ExecutionException {
return null;
}
#Override
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
}
I would go for TestExecutionListener,
example:
public class SchedulerExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(#NonNull TestContext testContext) {
try {
ScheduledAnnotationBeanPostProcessor schedulerProcessor = testContext.getApplicationContext().getBean(ScheduledAnnotationBeanPostProcessor.class);
schedulerProcessor.destroy();
} catch (Exception ignored) {
ignored.printStackTrace();
System.out.println(ignored.getMessage());
}
}
And then you add it to ur testClass
#TestExecutionListeners(listeners = SchedulerExecutionListener .class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
class Test {}
The way that I have solved this is for spring boot applications is by disabling the #EnableScheduling configuration for the test profile:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;
#Configuration
#Profile({"!test"})
#EnableScheduling
public class SchedulingConfiguration {
}
My application properties are stored in application.yml so simple add ConditionalOnProperty to scheduler:
#ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = true)
#EnableScheduling
And disable develop environment in application.yml:
environments:
development:
scheduling:
enabled: false

Spring Scheduler

I go through spring Scheduler support documentation.
where I found:
ScheduledFuture schedule (Runnable task, Date startTime);
But in case of #EnableScheduling there is no Thread Implementation in various examples.
Why??
any one can explain
Thanks in advance.
With #EnableScheduling you just enable the spring scheduler functionality.
To run a task you would annotate a public method with #Scheduled.
So you see that you dont need a runnable / thread for this as your annotabed method will be called using reflection.
#EnableScheduling
public class Tasks {
#Scheduled(... options here)
public void myTasks(){
//doSomethingHere...
}
}
our class must have at least these annotations :
package org.springframework.scheduling.annotation
#Configuration
#EnableScheduling
you can set it with a fixedDelay
#Scheduled(fixedDelay = 1000)
with initialDelay also:
#Scheduled(fixedDelay = 1000, initialDelay = 1000)
or with a fixedRate (when each execution of the task is independent)
#Scheduled(fixedRate = 1000)
you can also create it on runtime using
org.springframework.scheduling.annotation.SchedulingConfigurer
public class SchedulerContextconfig implements SchedulingConfigurer
#Override
public void configureTasks(ScheduledTaskRegistrar register) {
register.addCronTask(Runnable task, String expression)
}

Spring: Properly setup #ComponentScan

I have following set up for my Spring Application Context.
#Configuration
public class RmiContext {
#Bean
public RmiProxyFactoryBean service() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://127.0.1.1:1099/Service");
rmiProxy.setServiceInterface(Service.class);
return rmiProxy;
}
}
#Configuration
public class LocalContext {
#Bean
public Controller Controller() {
return new ControllerImpl();
}
}
#Configuration
#Import({RmiContext.class, LocalContext.class})
public class MainContext {
}
The above setup works fine, but I want to enable #ComponentScan annotating Controllers with #Component as there are many Controllers in my application which is tedious when declared one by one using #Bean.
#Configuration
#ComponentScan(basePackageClasses = {Controller.class})
public class LocalContext {
/* ... */
}
The problem is that when I do #ComponentScan(basePackageClasses = {Controller.class}), the previously fine working RmiProxyFactoryBean are not recognized or can't be created.
So, How do I configure my MainContext so that both beans via RMI and local beans are created?
#Configuration is also a candidate for component scan, so you can scan all the beans in RmiContext and all controllers in your controller package by:
#Configuration
#ComponentScan(basePackages = {"org.example.controllers", "package.of.RmiContext"})
public class MainContext {
}
--edit--
#Configuration is a candidate for component scan, here is the test case that works in my pc:
package scan.controllers;
#Controller
public class ExampleController {
}
package scan;
public interface RMIService {
}
package scan;
#Configuration
public class RmiContext {
#Bean
public RmiProxyFactoryBean service() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://127.0.1.1:1099/Service");
rmiProxy.setServiceInterface(RMIService.class);
rmiProxy.setLookupStubOnStartup(false);
return rmiProxy;
}
}
package scan;
#Configuration
//MainContext will auto scan RmiContext in package scan and all controllers in package scan.controllers
#ComponentScan(basePackages = {"scan", "scan.controllers"})
public class MainContext {
}
package scan;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={MainContext.class})
public class TestContext {
#Autowired private RMIService rmi;
#Autowired private ExampleController controller;
#Test
public void test() {
//both controller and rmi service are autowired as expected
assertNotNull(controller);
assertNotNull(rmi);
}
}
May be you could try using the base packages of your classes (RMI, Controller):
#ComponentScan(basePackages = {"your controller package", "your rmi package"})
If the RMI classes package is different than controller then they will fail to instantiate by spring.
If I understand you correctly, you use "#ComponentScan(basePackageClasses" but it is not detecting and registering your spring beans?
I had the same issue a few minutes ago. I did not give up and tried all funny guesses. One guess did it.
I had to add an XML component-scan entry in XML. I just put a dummy package there, like below:
component-scan base-package="dummy.filler.to.enable.component.scan"
It seems that the component-scan in XML enables the #ComponentScan.
[Late Edit: I noticed, my spring xml schema is spring 2.5. Anyway, I dont know if this matters. Best Regards.]

Resources