SSM重构到SpringBoot导致@Scheduled失效的坑

SSM重构到SpringBoot定时任务失效的坑

最近重构老项目, 把SSM的祖传代码升级成SpringBoot, 最后发现原先代码中使用了@Scheduled注解的定时任务不再执行.

背景

SSM项目中使用spring-task需要在xml中进行任务的配置, 比较繁琐, 但是在SpringBoot中使用就很简单了, 为了简单地说明问题, 下面都是伪代码:

定义一个任务的bean

@Component
public class TaskScheduler {
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendShareWechatMessageTask() {
        // 任务代码
    }
}

这里要说明两点:

  1. 任务类必须加上@Component或者使用其他注解, 让它能被spring识别为bean.
  2. 注解@Scheduled的任务方法不能有返回值

只用在启动类加上@EnableCaching注解, 定时任务即可使用

@EnableCaching
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

问题

在我做完上述步骤, 我的定时任务并没有生效, 我反复确认我的配置没有问题. 同时, 也采用了面向谷歌编程的方法, 看到很多解决方法本质都是配置问题, 但是我的配置并没有问题.于是, 我通过debug发现了问题所在:

ScheduledAnnotationBeanPostProcessor.java
`java
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
        AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
    Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
            (MethodIntrospector.MetadataLookup<Set<Scheduled>>) 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: " + targetClass);
        }
    }
    else {
        // Non-empty set of methods
        annotatedMethods.forEach((method, scheduledMethods) ->
                scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
        if (logger.isTraceEnabled()) {
            logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                    "': " + annotatedMethods);
        }
    }
}
return bean;

}


`ScheduledAnnotationBeanPostProcessor`是`spring-task`中处理`@Scheduled`注解任务的核心类,`ScheduledAnnotationBeanPostProcessor`实现了spring的`BeanPostProcessor`接口, 上面这个方法便是`BeanPostProcessor`中的重写方法, 简单来说, 这个方法在spring容器初始化完成之后会进行回调, 这个方法主要就是在回调时扫描回调的bean中,是否有`@Scheduled`注解的方法, 如果有就会加到定时任务中.但是可以看到最开始有一个判断, 有几个bean不会进行扫描, 其中一个便是`TaskScheduler`, 看到这里, 就知道为啥我定义的定时任务没生效了, 因为我定义定时任务的类也叫`TaskScheduler`, 但为啥在老的应用里就是正常的呢, 再看看老项目里相同的代码:

@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map<Method, Set> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup<Set>() {
@Override
public Set inspect(Method method) {
Set 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 {
// Non-empty set of methods
for (Map.Entry<Method, Set> 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);
}
}
}
return bean;
}
`
可以看到, 老的spring代码里并没有上述判断

总结

到这里, 问题解决, 这个问题花了好几个小时去找原因, 特意记录一次, 从此网上又多了一个spring-task不生效的原因.


   转载规则

本文不允许转载。
 上一篇
使用kubeadm搭建k8s 使用kubeadm搭建k8s
前言 本地搭建可以使用kind或者minikube,这两个工具可以快速搭建本地环境,可以用作练习,要搭建生产集群,还是得使用kubeadm。 Kubeadm是社区官方持续维护的集群搭建工具,在Kubernertes v1.13 版本的时候
2021-04-15
下一篇 
SpringBoot中的参数校验 SpringBoot中的参数校验
SpringBoot中的参数校验 背景 为了保证数据的正确性, 避免埋坑, 参数校验在日常业务开发中用得非常多, 在Spring中用得最多的就是使用JSR303– Bean Validation规范提供的校验, Hibernate Val