85

我正在使用@Scheduled注释在 Spring 中定义具有 cron 样式模式的计划作业。

cron 模式存储在配置属性文件中。实际上有两个属性文件:一个默认配置和一个依赖于环境的配置文件配置(例如 dev、test、prod customer 1、prod customer 2 等)并覆盖一些默认值。

我在我的 spring 上下文中配置了一个属性占位符 bean,它允许我使用${}样式占位符从我的属性文件中导入值。

作业 bean 如下所示:

@Component
public class ImagesPurgeJob implements Job {

    private Logger logger = Logger.getLogger(this.getClass());

    @Override
    @Transactional(readOnly=true)
    @Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
    public void execute() {
        //Do something
            //can use DAO or other autowired beans here
    }
}

我的上下文 XML 的相关部分:

<!-- Enable configuration of scheduled tasks via annotations -->
    <task:annotation-driven/>

<!-- Load configuration files and allow '${}' style placeholders -->
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:config/default-config.properties</value>
                <value>classpath:config/environment-config.properties</value>
            </list>
        </property>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="ignoreResourceNotFound" value="false"/>
    </bean>

我真的很喜欢这个。它使用最少的 XML 非常简单和干净。

但是我还有一个要求:在某些情况下,其中一些工作可以完全禁用。

因此,在我使用 Spring 管理它们之前,我手动创建了它们,并且在配置文件中有一个布尔参数和 cron 参数,用于指定是否必须启用作业:

jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?

我如何在 Spring 中使用此参数有条件地创建或直接忽略 bean,具体取决于此配置参数?

一个明显的解决方法是定义一个永远不会评估的 cron 模式,因此永远不会执行该作业。但是 bean 仍然会被创建并且配置会有点模糊,所以我觉得必须有一个更好的解决方案。

4

11 回答 11

47
@Component
public class ImagesPurgeJob implements Job {

    private Logger logger = Logger.getLogger(this.getClass());

    @Value("${jobs.mediafiles.imagesPurgeJob.enable}")
    private boolean imagesPurgeJobEnable;

    @Override
    @Transactional(readOnly=true)
    @Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
    public void execute() {

         //Do something
        //can use DAO or other autowired beans here
        if(imagesPurgeJobEnable){

            Do your conditional job here...

        }
    }
}
于 2013-08-23T15:49:39.073 回答
45

在 Spring 中禁用的最有效方法@Scheduled是将 cron 表达式设置为-

@Scheduled(cron = "-")
public void autoEvictAllCache() {
    LOGGER.info("Refresing the Cache Start :: " + new Date());
    activeMQUtility.sendToTopicCacheEviction("ALL");
    LOGGER.info("Refresing the Cache Complete :: " + new Date());
}

文档

CRON_DISABLED

public static final String CRON_DISABLED
表示禁用触发器的特殊 cron 表达式值:“-”。这主要用于 ${...} 占位符,允许外部禁用相应的计划方法。

自:5.1 另请参阅:ScheduledTaskRegistrar.CRON_DISABLED

于 2019-08-26T09:57:35.153 回答
32

您可以按条件将调度方法分组为服务数量并像这样初始化它们:

@Service
@ConditionalOnProperty("yourConditionPropery")
public class SchedulingService {

@Scheduled
public void task1() {...}

@Scheduled
public void task2() {...}

}
于 2015-02-27T19:20:49.190 回答
31

Spring Boot 提供了@ConditionalOnProperty,如果您使用的是 Spring Boot,这将是完美的。这个注解是@Conditional的一个特化,在 Spring 4.0.0 中引入。

假设您只是使用“常规”spring 而不是 Spring Boot,您可以创建自己的 Condition 实现以与 @Conditional 一起使用,这将模仿 Spring Boot 的 @ConditionalOnProperty。

于 2014-07-07T18:18:18.120 回答
18

如果您希望从属性切换 @EnableScheduling,您可以在 Spring Boot 中通过将 @EnableScheduling 注释移动到配置类并使用 @ConditionalOnProperty 来执行此操作,如下所示:

@Configuration
@EnableScheduling
@ConditionalOnProperty(prefix = "com.example.scheduling", name="enabled", havingValue="true", matchIfMissing = true)
public class SchedulingConfiguration {

}

这将禁用应用程序的调度。这在您希望能够运行应用程序一次或根据它的启动方式安排的情况下可能很有用。

来自威尔金索纳对这里的评论:https ://github.com/spring-projects/spring-boot/issues/12682

于 2018-07-05T19:45:16.593 回答
4

您的问题说明了 bean 的实际创建条件。如果您至少使用 Spring 3.1,则可以使用 @Profile 使用此参数轻松完成此操作。

请参阅此处的文档:http: //static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/context/annotation/Profile.html

于 2013-08-23T21:03:21.990 回答
2
@Component
public class CurrencySyncServiceImpl implements CurrencySyncService {

    private static Boolean isEnableSync;
    /**
     * Currency Sync FixedDelay in minutes
     */
    private static Integer fixedDelay;

    @Transactional
    @Override
    @Scheduled(fixedDelayString = "#{${currency.sync.fixedDelay}*60*1000}")
    public void sync() {
        if(CurrencySyncServiceImpl.isEnableSync) {
            //Do something
            //you can use DAO or other autowired beans here.
        }
    }

    @Value("${currency.sync.fixedDelay}")
    public void setFixedDelay(Integer fixedDelay) {
        CurrencySyncServiceImpl.fixedDelay = fixedDelay;
    }

    @Value("${currency.sync.isEnable}")
    public void setIsEnableSync(Boolean isEnableSync) {
        CurrencySyncServiceImpl.isEnableSync = isEnableSync;
    }
}
于 2017-08-30T08:16:03.880 回答
1

您还可以根据条件创建 Bean,并且该 Bean 可以具有 Scheduled 方法。

@Component
@Configuration
@EnableScheduling
public class CustomCronComponent {
    @Bean
    @ConditionalOnProperty(value = "my.cron.enabled", matchIfMissing = true, havingValue = "true")
    public MyCronTask runMyCronTask() {
        return new MyCronTask();
    }
}

@Component
public class MyCronTask {
    @Scheduled(cron = "${my.cron.expression}")
    public void run() {
        String a = "";
    }
}
于 2018-08-15T13:07:39.900 回答
1

请在另一个问题中查看我的回答。我认为这是解决它的最好方法。 如何停止使用@Scheduled 注解启动的计划任务?

定义一个自定义注释,如下所示。

@Documented
@Retention (RUNTIME)
@Target(ElementType.TYPE)
public @interface ScheduledSwitch {
    // do nothing
}

定义一个类实现org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor。

public class ScheduledAnnotationBeanPostProcessorCustom 
    extends ScheduledAnnotationBeanPostProcessor {

    @Value(value = "${prevent.scheduled.tasks:false}")
    private boolean preventScheduledTasks;

    private Map<Object, String> beans = new HashMap<>();

    private final ReentrantLock lock = new ReentrantLock(true);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        ScheduledSwitch switch = AopProxyUtils.ultimateTargetClass(bean)
            .getAnnotation(ScheduledSwitch.class);
        if (null != switch) {
            beans.put(bean, beanName);
            if (preventScheduledTasks) {
                return bean;
            }
        }
        return super.postProcessAfterInitialization(bean, beanName);
    }

    public void stop() {
        lock.lock();
        try {
            for (Map.Entry<Object, String> entry : beans.entrySet()) {
                postProcessBeforeDestruction(entry.getKey(), entry.getValue());
            }
        } finally {
            lock.unlock();
        }
    }

    public void start() {
        lock.lock();
        try {
            for (Map.Entry<Object, String> entry : beans.entrySet()) {
                if (!requiresDestruction(entry.getKey())) {
                    super.postProcessAfterInitialization(
                        entry.getKey(), entry.getValue());
                }
            }
        } finally {
            lock.unlock();
        }
    }

}

用配置中的自定义 bean 替换 ScheduledAnnotationBeanPostProcessor bean。

@Configuration
public class ScheduledConfig {

    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
        return new ScheduledAnnotationBeanPostProcessorCustom();
    }

}

将@ScheduledSwitch 注释添加到要阻止或停止@Scheduled 任务的bean。

于 2019-12-14T07:09:42.830 回答
0

我知道我的答案是 hack,但是提供一个从不执行的有效 cron 表达式可能会解决问题(在特定于环境的配置中),Quartz: Cron expression that will never execute

于 2018-04-05T08:36:42.960 回答
0

@Conditional我们可以使用注释禁用具有该计划方法的类的 bean 创建。这与@ConditionalOnProperty. 这用于有条件地将 bean 启动到 spring 上下文中。如果我们将值设置为false,那么 bean 将不会旋转并加载到 spring。下面是代码。

application.properties:

    com.boot.enable.scheduling=enable

健康)状况:

public class ConditionalBeans implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return "enabled".equalsIgnoreCase(context.getEnvironment().getProperty("com.boot.enable.scheduling"));
    }
}

我的日程安排课

@Service
@Conditional(ConditionalSchedules.class)
public class PrintPeriodicallyService {

    @Scheduled(fixedRate = 3000)
    public void runEvery3Seconds() {
        System.out.println("Current time : " + new Date().getTime());
    }
}

这种方法具有很大的灵活性,条件生成完全在我们的控制之下。

于 2020-05-08T08:21:40.877 回答