SSM重构到SpringBoot定时任务失效的坑
最近重构老项目, 把SSM的祖传代码升级成SpringBoot, 最后发现原先代码中使用了
@Scheduled
注解的定时任务不再执行.
背景
SSM项目中使用spring-task
需要在xml中进行任务的配置, 比较繁琐, 但是在SpringBoot中使用就很简单了, 为了简单地说明问题, 下面都是伪代码:
定义一个任务的bean
@Component
public class TaskScheduler {
@Scheduled(cron = "0/5 * * * * ?")
public void sendShareWechatMessageTask() {
// 任务代码
}
}
这里要说明两点:
- 任务类必须加上
@Component
或者使用其他注解, 让它能被spring识别为bean. - 注解
@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
new MethodIntrospector.MetadataLookup<Set
@Override
public Set
Set
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
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
不生效的原因.