目录
二、ScheduledAnnotationBeanPostProcessor
1.postProcessAfterInitialization
前言
spring中提供了定时任务的功能,简化了一些重复执行任务的编写过程,但同时也将内部细节隐藏了起来,因此,熟悉相关源码是必要的。定时任务的执行过程基本都是在ScheduledAnnotationBeanPostProcessor这个类中,基本是先扫描相关的注解,注册任务,然后将任务放入线程池中执行。
一、EnableScheduling
EnableScheduling注解的作用就是引入ScheduledAnnotationBeanPostProcessor这个类,下面的分析基本就是围绕这个类展开。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
二、ScheduledAnnotationBeanPostProcessor
1.postProcessAfterInitialization
在postProcessAfterInitialization方法中,首先是扫描带有scheduled注解的方法,然后使用相关方法构建Runnable对象,构建task,并将task写入相关list中,后期调用。
public Object postProcessAfterInitialization(Object bean, String beanName) {
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
@Override
public Set<Scheduled> inspect(Method method) {
//查找有相关注解的方法
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
if (annotatedMethods.isEmpty()) {
}
else {
// Non-empty set of methods
for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
//生成任务并注册
processScheduled(scheduled, method, bean);
}
}
}
}
return bean;
}
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
Assert.isTrue(method.getParameterTypes().length == 0,
"Only no-arg methods may be annotated with @Scheduled");
Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
//构造Runnable对象,run方法就是通过反射调用bean的method
Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);
// Check cron expression
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();
}
//将cron任务加入registrar的cronTasks中,方便后期调用
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
2.finishRegistration
finishRegistration方法在onApplicationEvent方法中调用,在容器刷新完成后调用.。在该方法中,首先是调用所有的SchedulingConfigure的configureTasks方法,可以实现手动添加定时任务,也可以自定义任务线程池,当系统内没有自定义的任务线程池时,程序会先查找有没有默认线程池,如果还是没有就会创建一个单线程线程池执行任务。
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<SchedulingConfigurer>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
//调用SchedulingConfigurer的相关方法,可以自己手动加入定时方法,进行自定义操作
for (SchedulingConfigurer configurer : configurers) {
configurer.configureTasks(this.registrar);
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
// Search for TaskScheduler bean... 对taskschedule进行赋值,前面taskschedule是null,所以手动写入的任务不会直接执行,当然在SchedulingConfigurer中定义线程池的话就不一样了
this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));
}
}
//将任务放入线程池中运行
this.registrar.afterPropertiesSet();
}
protected void scheduleTasks() {
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));
}
}
}
3.ReschedulingRunnable
程序最终会构建一个ReschedulingRunnable对象放入线程池中,在线程没有被取消时,会调用schedule方法,schedule方法会将当前对象再次放入线程池中,实现重复运行。
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
//调用jdk的定时任务线程池执行
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}
public void run() {
Date actualExecutionTime = new Date();
//调用代理的run方法,实际执行定时业务逻辑
super.run();
Date completionTime = new Date();
synchronized (this.triggerContextMonitor) {
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
if (!this.currentFuture.isCancelled()) {
//线程没有被取消时,调用schedule方法,再次将任务放入线程池中
schedule();
}
}
}
三、自定义任务线程池和定时任务
程序ThreadPoolTaskScheduler的默认核心线程数是1,其在执行时调用的是juc的ScheduledThreadPoolExecutor执行,基于一个无界队列实现了延时执行,如果不定义线程数,会导致线程池中只有一个线程运行。
public class SchedulerExecutorConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(threadPoolTaskScheduler());
}
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("taskExecutor-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
四、精准执行的定时任务
对于某些定时任务,要求在每分钟内都要执行,在没有配置自定义线程池的情况下,可能会与其他任务产生竞争,导致执行时间推迟。
@Scheduled(cron = "0 */1 * * * ?")
public void test1() throws InterruptedException {
System.out.println("test1运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
Thread.sleep(40000);
}
@Scheduled(cron = "0 */1 * * * ?")
public void test2() throws InterruptedException {
System.out.println("%%%%%%%%test2运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
Thread.sleep(40000);
}
如图所示定时任务,在只有test1运行时,能够保持一分钟执行一次。
在只有test1和test2同时运行时,由于test1和test2都需要40秒钟,导致两个任务无法在一分钟内完成,导致test1无法保证能够一分钟运行一次。
一般情况下,在使用自定义线程池,增加线程数量的情况下,能够改善这种情况,但是为了保证某些准确任务的独立运行,最好还是新建线程池,自定义定时任务线程池为其运行。
可以使用juc的scheduledExecutorService为重要任务独自建立一个线程池,同时通过监听器在容器启动的时候将任务放入线程池中执行。
@Component("BeanDefineConfigue2")
public class BeanDefineConfigue2 implements ApplicationListener<ApplicationEvent> {
boolean flag = false;
List<String> list = new ArrayList<String>();
ScheduledExecutorService scheduledExecutorService;
/**
* 当一个ApplicationContext被初始化或刷新触发
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(flag){
return;
}
if (event instanceof ContextRefreshedEvent) {
flag = true;
System.out.println("spring容易初始化完毕================================================888");
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println("test1运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
Thread.sleep(40000);
}
},1, 1, TimeUnit.MINUTES);
}
}
总结
本文主要对spring中定时任务的执行流程进行了分析,并且实现了自定义线程池,借助SchedulingConfigurer ,我们可以实现手动运行定时任务和手动取消,从而达到动态修改定时任务的目的。