您是否需要每天像闹钟一样在同一时间运行某个流程? 然后,Spring的预定任务适合您。 允许您使用@Scheduled
注释方法,以使其在指定的时间或内部间隔运行。 在本文中,我们将研究如何设置一个可以使用计划任务的项目,以及如何使用不同的方法来定义它们的执行时间。
我将在本文中使用Spring Boot,以使依赖关系变得简洁而又简单,这是因为可以对spring-boot-starter
依赖项进行调度,该依赖项将以某种方式包含在几乎每个Spring Boot项目中。 这使您可以使用任何其他启动程序依赖项,因为它们会引入spring-boot-starter
及其所有关系。 如果要包括确切的依赖项本身,请使用spring-context
。
您可以使用spring-boot-starter
。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.0.0.RC1</version>
</dependency>
或直接使用spring-context
。
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.3.RELEASE</version>
</dependency>
创建计划任务非常简单。 将@Scheduled
批注添加到希望自动运行的任何方法中,并将@EnableScheduling
包含在配置文件中。
因此,例如,您可能会遇到类似以下的内容。
@Component
public class EventCreator {private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class);private final EventRepository eventRepository;public EventCreator(final EventRepository eventRepository) {this.eventRepository = eventRepository;}@Scheduled(fixedRate = 1000)public void create() {final LocalDateTime start = LocalDateTime.now();eventRepository.save(new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000));LOG.debug("Event created!");}
}
这里有很多代码对于运行计划任务并不重要。 正如我在一分钟前说过的,我们需要在方法上使用@Scheduled
,它将自动开始运行。 因此,在上面的示例中, create
方法将每隔1000毫秒(1秒)开始运行,如注释的fixedRate
属性所示。 如果我们想更改其运行频率,则可以增加或减少fixedRate
时间,或者可以考虑使用可用的不同调度方法。
因此,您可能想知道这些其他方法是正确的吗? 好了,它们就在这里(我还将在此处包括fixedRate
)。
-
fixedRate
在fixedRate
调用之间以固定的毫秒周期执行该方法。 -
fixedRateString
一样的fixedRate
,但有一个字符串值来代替。 -
fixedDelay
在一次调用结束与下一次调用之间以固定的毫秒周期执行该方法。 -
fixedDelayString
一样fixedDelay
但一个字符串值来代替。 -
cron
使用类似cron的表达式来确定何时执行该方法(我们将在以后更深入地介绍此方法)。
@Scheduled
批注还有一些其他实用程序属性。
-
zone
指示将解析cron表达式的时区,如果不包括时区,它将使用服务器的默认时区。 因此,如果您需要它在特定时区运行,例如香港,则可以使用zone = "GMT+8:00"
。 -
initialDelay
延迟第一次执行计划任务的毫秒数,需要使用固定速率或固定延迟属性之一。 -
initialDelayString
同为initialDelay
但一个字符串值来代替。
以下是一些使用固定速率和延迟的示例。
@Scheduled(fixedRate = 1000)
与之前相同,每1秒运行一次。
@Scheduled(fixedRateString = "1000")
同上。
@Scheduled(fixedDelay = 1000)
在上一次调用完成后运行1秒。
@Scheduled(fixedRate = 1000, initialDelay = 5000)
每秒运行一次,但要等待5秒钟才能首次执行。
现在来看一下cron
属性,它可以对任务的计划进行更多控制,让我们定义任务运行的秒数,分钟数和小时数,甚至可以进一步指定任务的运行年限。
以下是构建cron表达式的组件的细分。
-
Seconds
值可以为0-59
或特殊字符, - * /
。 -
Minutes
值可以为0-59
或特殊字符, - * /
。 -
Hours
值可以为0-59
或特殊字符, - * /
。 -
Day of month
可以具有值1-31
或特殊字符, - * ? / LWC
, - * ? / LWC
。 -
Month
值可以为1-12
,JAN-DEC
或特殊字符, - * /
。 -
Day of week
可以具有值1-7
,SUN-SAT
或特殊字符, - * ? / LC #
, - * ? / LC #
。 -
Year
可以为空,值为1970-2099
或特殊字符, - * /
。
为了更加清楚起见,我将细目分类组合成一个由字段标签组成的表达式。
@Scheduled(cron = "[Seconds] [Minutes] [Hours] [Day of month] [Month] [Day of week] [Year]")
请不要在表达式中包括花括号(我使用它们使表达式更清晰)。
在继续之前,我们需要了解特殊字符的含义。
-
*
表示所有值,因此,如果在第二个字段中使用,则表示每秒或在天字段中使用,表示每天运行。 -
?
表示没有特定的值,并且可以在“月的天”或“星期几”字段中使用,其中使用一个会使另一个无效。 如果我们指定在一个月的15日触发,则一个?
将在“Day of week
字段中使用。 -
-
表示值的范围(例如,小时数字段中的1-3表示小时数1、2和3)。 -
,
代表附加价值,例如周一,周三,SUN在本周,说明此一天,在周一,周三和周日。 -
/
代表增量,例如,秒字段中的0/15从0(0、15、30和45)开始每15秒触发一次。 -
L
代表一周或一个月的最后一天。 请记住,在这种情况下,星期六是一周的结束,因此在“星期几”字段中使用L
将在星期六触发。 可以将其与月日字段中的数字结合使用,例如6L
代表月的最后一个星期五,或者L-3
这样的表达式表示月的最后一天。 如果我们在星期几字段中指定一个值,则必须使用?
在“月”字段中,反之亦然。 -
W
表示每月的最接近的工作日。 例如,如果15W
是工作日,则在每月的第15天触发,否则它将在最近的工作日运行。 该值不能在日期值列表中使用。 -
#
指定任务应该在星期几和星期几触发。 例如,5#2
表示该月的第二个星期四。 如果您指定的日期和星期溢出到下个月,则不会触发。
在这里可以找到有用的资源,其中的解释稍长一些,这有助于我撰写本文。
让我们来看几个例子。
@Scheduled(cron = "0 0 12 * * ?")
每天晚上12点开火。
@Scheduled(cron = "0 15 10 * * ? 2005")
2005年每天早上10:15触发。
@Scheduled(cron = "0/20 * * * * ?")
每20秒触发一次。
有关更多示例,请参阅我前面提到的链接, 此处再次显示。 幸运的是,如果您在编写一个简单的cron表达式时遇到麻烦,那么您应该可以在Google中找到所需的方案,因为有人可能已经在Stack Overflow上问了同样的问题。
要将上述内容与一个小的代码示例绑定在一起,请参见下面的代码。
@Component
public class AverageMonitor {private static final Logger LOG = LoggerFactory.getLogger(AverageMonitor.class);private final EventRepository eventRepository;private final AverageRepository averageRepository;public AverageMonitor(final EventRepository eventRepository, final AverageRepository averageRepository) {this.eventRepository = eventRepository;this.averageRepository = averageRepository;}@Scheduled(cron = "0/20 * * * * ?")public void publish() {final double average =eventRepository.getAverageValueGreaterThanStartTime("An event type", LocalDateTime.now().minusSeconds(20));averageRepository.save(new Average(new AverageKey("An event type", LocalDateTime.now()), average));LOG.info("Average value is {}", average);}
}
在这里,我们有一个类,每20秒向Cassandra查询一次同一时间段内事件的平均值。 同样,这里的大多数代码都是@Scheduled
批注中的噪音,但在野外看到它可能会有所帮助。 此外,如果您观察到这一情况,则对于每20秒运行一次的用例,在此处使用频繁运行任务的情况下,使用fixedRate
以及可能使用fixedDelay
属性而不是cron
更为合适。
@Scheduled(fixedRate = 20000)
是上面使用的cron表达式的fixedRate
等效项。
我前面提到的最终要求是将@EnableScheduling
批注添加到配置类。
@SpringBootApplication
@EnableScheduling
public class Application {public static void main(final String args[]) {SpringApplication.run(Application.class);}
}
作为一个很小的Spring Boot应用程序,我已将@EnableScheduling
批注附加到主@SpringBootApplication
类。
总而言之,我们可以安排任务使用@Scheduled
注释以及执行之间的毫秒级速率或cron表达式来触发,以实现无法用前者表达的更佳时序。 对于需要非常频繁运行的任务,使用fixedRate
或fixedDelay
属性就足够了,但是一旦执行之间的时间变大,则很难快速确定所定义的时间。 发生这种情况时,应使用cron
属性以更好地了解计划的时间。
这篇文章中使用的少量代码可以在我的GitHub上找到 。
如果您发现这篇文章很有帮助,并希望在我撰写新教程时保持最新,请在Twitter上@LankyDanDev关注我。
翻译自: https://www.javacodegeeks.com/2018/02/running-time-springs-scheduled-tasks.html