spring 定时器任务深入理解
spring配置文件中配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:task="http://www.springframework.org/schema/task"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.2.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-4.2.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.2.xsdhttp://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task-3.0.xsd"><bean class="com.wjz.quartz.RepayQuartz"/><task:annotation-driven/></beans>
定时任务处理类
packagecom.wjz.quartz;importorg.springframework.scheduling.annotation.Scheduled;public classRepayQuartz {@Scheduled(cron="0/10 * * * * ?")public voidrepay() {System.out.println("sping 定时任务");} }
<task:annotation-driven/>标签使用TaskNamespaceHandler 来处理
public class TaskNamespaceHandler extendsNamespaceHandlerSupport {@Overridepublic voidinit() { // 注册了一些解析器其中AnnotationDrivenBeanDefinitionParser解析@Scheduled注解this.registerBeanDefinitionParser("annotation-driven", newAnnotationDrivenBeanDefinitionParser());this.registerBeanDefinitionParser("executor", newExecutorBeanDefinitionParser());this.registerBeanDefinitionParser("scheduled-tasks", newScheduledTasksBeanDefinitionParser());this.registerBeanDefinitionParser("scheduler", newSchedulerBeanDefinitionParser());}}
注册了两个后置处理器
org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor
org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
我们主要来看ScheduledAnnotationBeanPostProcessor
我们可以看到他实现了BeanPostProcesser,ApplicationListener接口
先执行初始化后的后置处理
@Overridepublic Object postProcessAfterInitialization(finalObject bean, String beanName) {Class<?> targetClass =AopUtils.getTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass)) { // 获得bean的所有的方法,获得方法信息和@Scheduled注解信息Map<Method, Set<Scheduled>> annotatedMethods =MethodIntrospector.selectMethods(targetClass,new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {@Overridepublic Set<Scheduled>inspect(Method method) {Set<Scheduled> scheduledMethods =AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledMethods.isEmpty() ? scheduledMethods : null);}});if(annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);if(logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " +bean.getClass());}}else{for (Map.Entry<Method, Set<Scheduled>>entry : annotatedMethods.entrySet()) {Method method=entry.getKey();for(Scheduled scheduled : entry.getValue()) { // 处理加工方法和注解信息,后文详解processScheduled(scheduled, method, bean);}}if(logger.isDebugEnabled()) {logger.debug(annotatedMethods.size()+ " @Scheduled methods processed on bean '" + beanName +"': " +annotatedMethods);}}}returnbean;}
protected voidprocessScheduled(Scheduled scheduled, Method method, Object bean) {try{Method invocableMethod=AopUtils.selectInvocableMethod(method, bean.getClass()); // 创建了一个Runnable对象,主要是用来反射定时方法的,后文详解#1Runnable runnable= newScheduledMethodRunnable(bean, invocableMethod);boolean processedSchedule = false;String errorMessage="Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";// 初始化一个定时任务集合 Set<ScheduledTask> tasks = this.scheduledTasks.get(bean);if (tasks == null) {tasks= new LinkedHashSet<ScheduledTask>(4);this.scheduledTasks.put(bean, tasks);}//Determine initial delaylong initialDelay =scheduled.initialDelay();String initialDelayString=scheduled.initialDelayString();if(StringUtils.hasText(initialDelayString)) {Assert.isTrue(initialDelay< 0, "Specify 'initialDelay' or 'initialDelayString', not both");if (this.embeddedValueResolver != null) {initialDelayString= this.embeddedValueResolver.resolveStringValue(initialDelayString);}try{initialDelay=Long.parseLong(initialDelayString);}catch(NumberFormatException ex) {throw newIllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");}}//解析表达式String cron =scheduled.cron();if(StringUtils.hasText(cron)) {Assert.isTrue(initialDelay== -1, "'initialDelay' not supported for cron triggers");processedSchedule= true;String zone=scheduled.zone();if (this.embeddedValueResolver != null) {cron= this.embeddedValueResolver.resolveStringValue(cron);zone= this.embeddedValueResolver.resolveStringValue(zone);}TimeZone timeZone;if(StringUtils.hasText(zone)) {timeZone=StringUtils.parseTimeZoneString(zone);}else{ // 没有指定时区的话使用默认时区timeZone=TimeZone.getDefault();} // 将表达式解析成任务计划表填充到任务集合中,后文详解#2tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, newCronTrigger(cron, timeZone))));}//At this point we don't need to differentiate between initial delay set or not anymoreif (initialDelay < 0) {initialDelay= 0;}//Check fixed delaylong fixedDelay =scheduled.fixedDelay();if (fixedDelay >= 0) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule= true;tasks.add(this.registrar.scheduleFixedDelayTask(newIntervalTask(runnable, fixedDelay, initialDelay)));}String fixedDelayString=scheduled.fixedDelayString();if(StringUtils.hasText(fixedDelayString)) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule= true;if (this.embeddedValueResolver != null) {fixedDelayString= this.embeddedValueResolver.resolveStringValue(fixedDelayString);}try{fixedDelay=Long.parseLong(fixedDelayString);}catch(NumberFormatException ex) {throw newIllegalArgumentException("Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");}tasks.add(this.registrar.scheduleFixedDelayTask(newIntervalTask(runnable, fixedDelay, initialDelay)));}//Check fixed ratelong fixedRate =scheduled.fixedRate();if (fixedRate >= 0) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule= true;tasks.add(this.registrar.scheduleFixedRateTask(newIntervalTask(runnable, fixedRate, initialDelay)));}String fixedRateString=scheduled.fixedRateString();if(StringUtils.hasText(fixedRateString)) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule= true;if (this.embeddedValueResolver != null) {fixedRateString= this.embeddedValueResolver.resolveStringValue(fixedRateString);}try{fixedRate=Long.parseLong(fixedRateString);}catch(NumberFormatException ex) {throw newIllegalArgumentException("Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");}tasks.add(this.registrar.scheduleFixedRateTask(newIntervalTask(runnable, fixedRate, initialDelay)));}//Check whether we had any attribute set Assert.isTrue(processedSchedule, errorMessage);}catch(IllegalArgumentException ex) {throw newIllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " +ex.getMessage());}}
书接前文#1
Runnable的run方法中是定时方法反射
@Overridepublic voidrun() {try{ReflectionUtils.makeAccessible(this.method);this.method.invoke(this.target);}catch(InvocationTargetException ex) {ReflectionUtils.rethrowRuntimeException(ex.getTargetException());}catch(IllegalAccessException ex) {throw newUndeclaredThrowableException(ex);}}
书接前文#2
创建一个CronTask任务对象,其中有Runnable对象和CronTrigger触发对象(表达式,时区),后文详解触发器的构造#2-1
publicScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask= this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask= newScheduledTask();newTask= true;}if (this.taskScheduler != null) { // 执行定时任务scheduledTask.future= this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else{ // 添加定时任务addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);}
再看监听处理
@Overridepublic voidonApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {//Running in an ApplicationContext -> register tasks this late...//giving other ContextRefreshedEvent listeners a chance to perform//their work at the same time (e.g. Spring Batch's job registration). finishRegistration();}}
this.registrar.afterPropertiesSet();
protected voidscheduleTasks() {if (this.taskScheduler == null) { // 初始化一个线程池调度器this.localExecutor =Executors.newSingleThreadScheduledExecutor(); // 指定了任务调度器this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);}if (this.triggerTasks != null) {for (TriggerTask task : this.triggerTasks) {addScheduledTask(scheduleTriggerTask(task));}}if (this.cronTasks != null) {for (CronTask task : this.cronTasks) {addScheduledTask(scheduleCronTask(task));}}if (this.fixedRateTasks != null) {for (IntervalTask task : this.fixedRateTasks) {addScheduledTask(scheduleFixedRateTask(task));}}if (this.fixedDelayTasks != null) {for (IntervalTask task : this.fixedDelayTasks) {addScheduledTask(scheduleFixedDelayTask(task));}}}
publicScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask= this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask= newScheduledTask();newTask= true;} // 这次指定了ConcurrentTaskScheduler任务调度器,开始调度定时任务if (this.taskScheduler != null) {scheduledTask.future= this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else{addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);}
@Overridepublic ScheduledFuture<?>schedule(Runnable task, Trigger trigger) {try{if (this.enterpriseConcurrentScheduler) {return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);}else{ErrorHandler errorHandler= (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true)); // 这里就是核心的地方了, Runnable的run方法内,定时调用自己的run方法实现功能,巧妙之极return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();}}catch(RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " +task, ex);}}
public ScheduledFuture<?>schedule() {synchronized (this.triggerContextMonitor) { // 拿到下一次执行任务的时间,后文详解#2-2this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);if (this.scheduledExecutionTime == null) {return null;} // 固定延迟即延迟多长时间执行long initialDelay = this.scheduledExecutionTime.getTime() -System.currentTimeMillis();this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);return this;}}@Overridepublic voidrun() {Date actualExecutionTime= newDate(); // 这就是上文所提到的定时方法反射执行定时业务方法super.run();Date completionTime= newDate();synchronized (this.triggerContextMonitor) { // 记录上一次任务执行的时间this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);if (!this.currentFuture.isCancelled()) { // 继续无限调度schedule();}}}
书接前文#2-1
CronTrigger触发对象的构造,初始化了计划时间表生成器
publicCronTrigger(String expression, TimeZone timeZone) {this.sequenceGenerator = newCronSequenceGenerator(expression, timeZone);}
publicCronSequenceGenerator(String expression, TimeZone timeZone) {this.expression =expression;this.timeZone =timeZone; // 解析表达式生成计划时间表parse(expression);}
private void parse(String expression) throwsIllegalArgumentException {String[] fields= StringUtils.tokenizeToStringArray(expression, " ");if (!areValidCronFields(fields)) {throw newIllegalArgumentException(String.format("Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));} // 主要使用了BitSet来制造计划时间表setNumberHits(this.seconds, fields[0], 0, 60);setNumberHits(this.minutes, fields[1], 0, 60);setNumberHits(this.hours, fields[2], 0, 24);setDaysOfMonth(this.daysOfMonth, fields[3]);setMonths(this.months, fields[4]);setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);if (this.daysOfWeek.get(7)) {//Sunday can be represented as 0 or 7this.daysOfWeek.set(0);this.daysOfWeek.clear(7);}}
private void setNumberHits(BitSet bits, String value, int min, intmax) {String[] fields= StringUtils.delimitedListToStringArray(value, ",");for(String field : fields) { // 检查是否包含'/',不包含的话列出最小值到最大值的范围if (!field.contains("/")) {//Not an incrementer so it must be a range (possibly empty)int[] range =getRange(field, min, max);bits.set(range[0], range[1] + 1);}else{// 包含'/'的话列出每xx秒(或分、时)的时间表如{0,10,20,30,40,50}String[] split= StringUtils.delimitedListToStringArray(field, "/");if (split.length > 2) {throw new IllegalArgumentException("Incrementer has more than two fields: '" +field+ "' in expression \"" + this.expression + "\"");}int[] range = getRange(split[0], min, max);if (!split[0].contains("-")) {range[1] = max - 1;}int delta = Integer.valueOf(split[1]);if (delta <= 0) {throw new IllegalArgumentException("Incrementer delta must be 1 or higher: '" +field+ "' in expression \"" + this.expression + "\"");} // 以一定数值叠加为BitSet赋值for (int i = range[0]; i <= range[1]; i +=delta) {bits.set(i);}}}}
private int[] getRange(String field, int min, intmax) {int[] result = new int[2]; // 包含*就是最大到最小if (field.contains("*")) {result[0] =min;result[1] = max - 1;returnresult;} // 不包含'/'和'-'返回值if (!field.contains("-")) {result[0] = result[1] =Integer.valueOf(field);}else{String[] split= StringUtils.delimitedListToStringArray(field, "-");if (split.length > 2) {throw new IllegalArgumentException("Range has more than two fields: '" +field+ "' in expression \"" + this.expression + "\"");}result[0] = Integer.valueOf(split[0]);result[1] = Integer.valueOf(split[1]);}if (result[0] >= max || result[1] >=max) {throw new IllegalArgumentException("Range exceeds maximum (" + max + "): '" +field+ "' in expression \"" + this.expression + "\"");}if (result[0] < min || result[1] <min) {throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +field+ "' in expression \"" + this.expression + "\"");}if (result[0] > result[1]) {throw new IllegalArgumentException("Invalid inverted range: '" + field +"' in expression \"" + this.expression + "\"");}returnresult;}
书接前文#2-2
拿到下一个执行调度的时间
@OverridepublicDate nextExecutionTime(TriggerContext triggerContext) { // 拿到上一次执行调度的时间Date date=triggerContext.lastCompletionTime();if (date != null) {Date scheduled=triggerContext.lastScheduledExecutionTime();if (scheduled != null &&date.before(scheduled)) {date =scheduled;}}else{ // 没拿到的话就拿当前时间date= newDate();} // 拿到一下一次执行调度的时间return this.sequenceGenerator.next(date);}
publicDate next(Date date) {Calendar calendar= newGregorianCalendar();calendar.setTimeZone(this.timeZone);calendar.setTime(date);calendar.set(Calendar.MILLISECOND, 0);long originalTimestamp =calendar.getTimeInMillis(); // 为日历设置下一次执行调度的时间doNext(calendar, calendar.get(Calendar.YEAR));if (calendar.getTimeInMillis() ==originalTimestamp) {calendar.add(Calendar.SECOND, 1);doNext(calendar, calendar.get(Calendar.YEAR));}// 返回设置好下一次调度的日历时间returncalendar.getTime();}
private void doNext(Calendar calendar, intdot) {List<Integer> resets = new ArrayList<Integer>();// 拿到当前时间的秒数int second =calendar.get(Calendar.SECOND);List<Integer> emptyList =Collections.emptyList(); // 拿到下一次执行调度的秒数int updateSecond = findNext(this.seconds, second, calendar, Calendar.SECOND, Calendar.MINUTE, emptyList);if (second ==updateSecond) {resets.add(Calendar.SECOND);}int minute =calendar.get(Calendar.MINUTE);int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets);if (minute ==updateMinute) {resets.add(Calendar.MINUTE);}else{doNext(calendar, dot);}int hour =calendar.get(Calendar.HOUR_OF_DAY);int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets);if (hour ==updateHour) {resets.add(Calendar.HOUR_OF_DAY);}else{doNext(calendar, dot);}int dayOfWeek =calendar.get(Calendar.DAY_OF_WEEK);int dayOfMonth =calendar.get(Calendar.DAY_OF_MONTH);int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets);if (dayOfMonth ==updateDayOfMonth) {resets.add(Calendar.DAY_OF_MONTH);}else{doNext(calendar, dot);}int month =calendar.get(Calendar.MONTH);int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets);if (month !=updateMonth) {if (calendar.get(Calendar.YEAR) - dot > 4) {throw new IllegalArgumentException("Invalid cron expression \"" + this.expression +"\" led to runaway search for next trigger");}doNext(calendar, dot);}}
private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List<Integer>lowerOrders) { // 拿到下一个执行调度的秒数如当前时间的秒数是23任务是每10秒调度则拿到30,如果是58则返回-1int nextValue =bits.nextSetBit(value); if (nextValue == -1) { // 加一分(时,日)calendar.add(nextField,1); // 重置秒(分、时)reset(calendar, Arrays.asList(field));nextValue= bits.nextSetBit(0);}if (nextValue !=value) { // 为日历设置下一个执行调度的秒(分、时)数calendar.set(field, nextValue);reset(calendar, lowerOrders);}returnnextValue;}
转载于:https://www.cnblogs.com/BINGJJFLY/p/7485599.html
spring 定时器任务深入理解相关推荐
- linux定时器多次,Spring 定时器执行两次
Spring错误笔记 Spring定时器执行两次因为导入了两次 关于配置文件如下 对应的类有个定时执行检查的动作,但是动作中的日志每次输出两遍,一开始以为是log4j的输出导致的两条,找了半天没办法还 ...
- Spring定时器的使用-多实例下定时重建索引
2019独角兽企业重金招聘Python工程师标准>>> 前几天接到项目需要定时重建索引的任务,一开始试了试Java自带的Timer,不知道是不是自己对Timer的了解还不够的原因,感 ...
- Spring 定时器
本文向您介绍Spring定时器的两种实现方式,包括Java Timer定时和Quartz定时器,两种Spring定时器的实现方式各有优点,可结合具体项目考虑是否采用. 有两种流行Spring定时器配置 ...
- spring定时器分析
spring定时器如何使用? 步骤1. 定义job bean 1.TaskScheduler构造 2.初始化相关服务 <bean id="xxxxScheduler" cla ...
- spring定时器(@Scheduled)
spring定时器需要额外添加下面配置 一.配置文件 xmlns 额外添加下面的内容: xmlns:task="http://www.springframework.org/schema/t ...
- Java怎么使用spring定时器_浅析spring定时器的使用
原生的Java定时器 使用Java.util包下的定时器也很简单,具体代码如下: //设置定时器开始时间 Date time = sdf.parse("2020-10-01 16:40:00 ...
- spring 定时器注释_带注释的控制器– Spring Web / Webflux和测试
spring 定时器注释 Spring Webflux和Spring Web是两个完全不同的Web堆栈. 但是, Spring Webflux继续支持基于注释的编程模型 使用这两个堆栈定义的端点可能看 ...
- [spring-framework]Spring定时器的配置和使用
开发中我们常常会做一些定时任务,这些任务有开始时间,并会按一定的周期或规则执行.如此我们在Java程序开发中使用定时器来处理定时任务. <!-- MessageRequestTask类中包含了m ...
- spring定时器,定时器一次执行两次的问题
Spring 定时器 方法一:注解形式 配置文件头加上如下: xmlns:task="http://www.springframework.org/schema/task"http ...
最新文章
- 用心真诚对待,懂你的人
- 《Java8实战》笔记(08):重构、测试和调试
- JavaScript-字符串
- C#中委托和事件的区别
- 使用ASP.NET Core开始使用gRPC客户端和服务器
- css两列显示,div+css如何控制信息分两列显示?
- 动态规划解决约瑟夫环问题
- 猿创征文|时间序列分析算法之平稳时间序列预测算法和自回归模型(AR)详解+Python代码实现
- Java中的JDK动态代理
- 复试口语常见话题整理以及华师18 19年topic
- FFmpeg学习(四)-- libavformat 代码组成
- UML之Astah的基本使用教程-4(活动图、序列图、Stereotype Icon)
- linux运行海康的sdk,海康摄像头SDK在Linux、windows下的兼容问题(二)已解决
- 台式计算机开机风扇不转,电脑开机显卡风扇不转是怎么回事|电脑开机风扇不转的解决方法...
- 增量式编码器 绝对值编码器
- pixhawk之NSH调试
- CAD 二次开发 图层操作(3)取得指定图层下的所有对象id
- 文件实时同步备份软件那个比较好用?
- 人工智能研究的内容:_更深入:人工智能研究的思想史
- 【面试】TCP、UDP、Socket、HTTP网络编程面试题
热门文章
- Android 框架炼成 教你怎样写组件间通信框架EventBus
- android 设颜色透明值
- USB HID report descriptor
- 快要普通话考试了,急需最后一题的根据话题自由讲话的演讲稿!(不要那些已经被用过...
- Mysql数据库 sql 语句调优
- WPF入门知识(学习)
- AjaxControlToolkit中CalendarExtender日历控件的用法
- mysql函数GROUP_CONCAT,Cast,convert
- 把button伪装成超链接
- dreamweaver在onLoad运行RecordsetFind.htm时出错