Oolong Caused By The Annotation Lazy And Schedule

2017/07/31

遇到了Spring中一个由于初始化顺序不同导致的问题。

和同学一起Spring Scheduler写了一个包含定时任务的Component,简化如下:

package hello;

import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class Component1 {

    // SpringApplicationContext是一个查找Bean的工具类
    SomeBean someBean = SpringApplicationContext.getBean(SomeBean.class);

    @Scheduled(fixedRate = 1000L)
    public void schedule() {
        // 执行业务逻辑
        someBean.doSomething();
        System.out.println("Scheduled");
    }
}

由于SomeBean和Component1的实例化顺序不确定,SomeBean晚于Component1初始化的情况下,SpringApplicationContext.getBean(SomeBean.class)会失败,这样就不得不考虑让这一步骤延后,以避免初始化失败。

于是想到了使用Lazy注解延迟Component1的实例化。使用Lazy注解后,Component1将不会在ApplicationContext启动时提前被实例化,而是第一次向容器通过getBean索取bean时实例化的。

@Lazy
@Component
public class Component1 {

以为这样就解决了应用启动失败的问题,谁知这样导致schedule定时任务不生效了。

仔细看了@Scheduled注解的工作原理后,发现它的核心类是ScheduledAnnotationBeanPostProcessor。其中的核心方法是postProcessAfterInitialization,负责@Schedule注解的扫描,构建ScheduleTask。而这个类实现的是BeanPostProcessor接口,仅在类被实例化之后才被调用。

原因缕清楚了,使用了@Lazy注解的Component1没有在任何其他地方被显式实例化,因为无法被扫描到其内部的@Scheduled注解,观察到的结果就是:定时任务不生效。

有了这些分析后就好办了:既让Component1实例化,使得@Scheduled生效,又让SomeBean能够被找到。

最终使用了如下写法:

package hello;

import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class Component1 {

    @Scheduled(fixedRate = 1000L)
    public void schedule() {
        // SpringApplicationContext是一个查找Bean的工具类
        SomeBean someBean = SpringApplicationContext.getBean(SomeBean.class);   
        // 执行业务逻辑
        someBean.doSomething();
        System.out.println("Scheduled");
    }
}