Spring Schedule不生效问题分析

开始

本文记录使用Spring @Schedule 配置不生效的问题分析.

项目环境

先说下项目环境,项目中使用的spring版本是4.3.8.

启用Spring schedule.

@Configuration
@EnableScheduling
public class AppConfig {
}


Task 类

public class TestJob {
/**
* job
*/
@Scheduled(fixedDelay = 3000)
public void task1(){
// actualBizLogic();
}
}

项目启动后任务并没有按照预期执行.

分析

spring通过@Scheduled 配置job,通过@EnableScheduling 启用计划任务调度

@EnableScheduling源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

该注解通过@Import导入SchedulingConfiguration 配置


@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
//这里配置了用于处理@Scheduled 注解的BeanPostProcessor (bean后置处理器)
return new ScheduledAnnotationBeanPostProcessor();
}
}

BeanPostProcessor 是Spring的中的扩展点,可以对bean初始化后做一些增强操作(eg:声明式事务的实现)

ScheduledAnnotationBeanPostProcessor 的关键代码


@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
//获取bean的目标类型 ,注意: 因为bean可能会被其他BeanPostProcessor 增强生成代理的bean,所以这里需要获取bean的实际类型
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
//获取目标类中所有带有@Scheduled 注解的方法
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()) {
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<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);
}
}
}
return bean;
}

问题就出现在上述代码中

Class<?> targetClass = AopUtils.getTargetClass(bean);

我们查看AopUtils#getTargetClass 源码


public static Class<?> getTargetClass(Object candidate) {
Assert.notNull(candidate, "Candidate object must not be null");
Class<?> result = null;
//如果目标对象是对代理过的对象
if (candidate instanceof TargetClassAware) {
//获取targetClass
result = ((TargetClassAware) candidate).getTargetClass();
}
if (result == null) {
result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
}
return result;
}

正常情况下这里不会有问题,但是当我们的bean 被多重代理后,获取到target并不是真正的目标对象,因此获取到的targetClass并不是实际的class

最终 MethodIntrospector.selectMethods 去获取 带有@Scheduled 的方法自然获取不到,我们的方法也不会加入任务调度.

这个问题在 spring 4.3.14 版本中被修复了

修复后的源码

//这个方法可以获取到被多重代理的bean的实际类
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);

相关Issue Combining @Retryable and @Scheduled/@JmsListener doesn’t work [SPR-16196]