1. 程式人生 > >spring boot.定時任務問題記錄(TaskScheduler/ScheduledExecutorService異常)

spring boot.定時任務問題記錄(TaskScheduler/ScheduledExecutorService異常)

context 人員 name bst pool .config implement per not

一、背景

spring boot的定時任務非常簡單,只需要在啟動類中加上@EnableScheduling註解,然後在對應的方法上配置@Scheduled就可以了,系統會自動處理並按照Scheduled中的配置定時執行方法。

但是在啟動項目的時候,發生了很詭異的現象,有兩個TaskScheduler/ScheduledExecutorService的異常打印了出來。但是系統並沒有受影響,依然正常啟動,而且定時任務也是正常執行。

2018-09-29 15:54:05,187 DEBUG main org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor [//] Could not find default TaskScheduler bean
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.scheduling.TaskScheduler‘ available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:996)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.resolveSchedulerBean(ScheduledAnnotationBeanPostProcessor.java:278)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:221)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:200)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:94)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:383)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:337)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:882)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:144)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:545)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151)
    at com.cmft.RunApp.main(RunApp.java:34)
2018-09-29 15:54:05,190 DEBUG main org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor [//] Could not find default ScheduledExecutorService bean
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘java.util.concurrent.ScheduledExecutorService‘ available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:996)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.resolveSchedulerBean(ScheduledAnnotationBeanPostProcessor.java:278)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:241)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:200)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:94)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:383)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:337)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:882)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:144)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:545)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151)
    at com.cmft.RunApp.main(RunApp.java:34)
2018-09-29 15:54:05,190 INFO  main org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor [//] No TaskScheduler/ScheduledExecutorService bean found for scheduled processing

二、分析

雖然沒有影響系統,但是有異常看著總歸很難受,於是嘗試去探究下這個異常的原因。

通過觀察我們這兩個異常是日誌模塊打印出來的debug級別日誌

2018-09-29 15:54:05,187 DEBUG main org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor [//] Could not find default TaskScheduler bean
2018-09-29 15:54:05,190 DEBUG main org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor [//] Could not find default ScheduledExecutorService bean

而且都是在ScheduledAnnotationBeanPostProcessor類中打印出來。看類的名字我們大概能猜到這是@Scheduled註解的處理類。

根據日誌內容我們定位到這個問題是由finishRegistration()方法打印出來。

private void finishRegistration() {
        if (this.scheduler != null) {
            this.registrar.setScheduler(this.scheduler);
        }

        if (this.beanFactory instanceof ListableBeanFactory) {
            Map<String, SchedulingConfigurer> configurers = ((ListableBeanFactory)this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
            Iterator var2 = configurers.values().iterator();

            while(var2.hasNext()) {
                SchedulingConfigurer configurer = (SchedulingConfigurer)var2.next();
                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 {
                this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(TaskScheduler.class, false));
            } catch (NoUniqueBeanDefinitionException var8) {
                try {
                    this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(TaskScheduler.class, true));
                } catch (NoSuchBeanDefinitionException var7) {
                    if (this.logger.isInfoEnabled()) {
                        this.logger.info("More than one TaskScheduler bean exists within the context, and none is named ‘taskScheduler‘. Mark one of them as primary or name it ‘taskScheduler‘ (possibly as an alias); or implement the SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " + var8.getBeanNamesFound());
                    }
                }
            } catch (NoSuchBeanDefinitionException var9) {
                this.logger.debug("Could not find default TaskScheduler bean", var9);

                try {
                    this.registrar.setScheduler(this.resolveSchedulerBean(ScheduledExecutorService.class, false));
                } catch (NoUniqueBeanDefinitionException var5) {
                    try {
                        this.registrar.setScheduler(this.resolveSchedulerBean(ScheduledExecutorService.class, true));
                    } catch (NoSuchBeanDefinitionException var4) {
                        if (this.logger.isInfoEnabled()) {
                            this.logger.info("More than one ScheduledExecutorService bean exists within the context, and none is named ‘taskScheduler‘. Mark one of them as primary or name it ‘taskScheduler‘ (possibly as an alias); or implement the SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " + var5.getBeanNamesFound());
                        }
                    }
                } catch (NoSuchBeanDefinitionException var6) {
                    this.logger.debug("Could not find default ScheduledExecutorService bean", var6);
                    this.logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
                }
            }
        }

        this.registrar.afterPropertiesSet();
    }

看代碼我們會發現,spring會從先從註冊過的bean中找任務調度器TaskScheduler

 this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(TaskScheduler.class, false));

如果獲取不到會拋出異常,然後打印出來

this.logger.debug("Could not find default TaskScheduler bean", var9);

然後繼續尋找定時任務的執行類ScheduledExecutorService

this.registrar.setScheduler(this.resolveSchedulerBean(ScheduledExecutorService.class, false));

如果找不到,會繼續拋出異常並打印出來

catch (NoSuchBeanDefinitionException var6) {
                    this.logger.debug("Could not find default ScheduledExecutorService bean", var6);
                    this.logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
                }

如此,便看到開頭我們看到的兩個異常。

看後面代碼我們會發現,如果註冊的bean中找不到,會調用scheduleTasks()方法初始化TaskScheduler,所以這兩個異常並不會影響系統。可能spring 的開發者是想借此提醒開發人員要自己在系統中註冊要使用的bean,而不是依賴框架來默認初始化。

解決辦法

修改日誌級別

第一種是眼不見為凈法,簡單粗暴地修改日誌級別就好。

log4j.logger.org.springframework.scheduling = INFO

這樣debug級別的日誌就不會被打印出來,只有最後的info級別日誌才會被打印。

註冊TaskScheduler

既然提示的異常是註冊的bean中找不到TaskScheduler,那麽我們就註冊TaskScheduler。

    @Bean
    public TaskScheduler scheduledExecutorService() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(8);
        scheduler.setThreadNamePrefix("scheduled-thread-");
        return scheduler;
    }

閱讀原文

spring boot.定時任務問題記錄(TaskScheduler/ScheduledExecutorService異常)