@RefreshScope和@Scheduled

@RefreshScope

功能:刷新配置信息,下例可以自动从配置文件中刷新到最新的startTask值
TaskConfig,java

/**
 * @author yaLong
 * @date 2022/2/12
 */
@RefreshScope
@Configuration
public class TaskConfig {
    /**
     * 自动任务调度的开关
     */
    @Value("${auto-task.start:false}")
    private Boolean startTask;

    public Boolean getStartTask() {
        return startTask;
    }
}

@Scheduled

定时任务,下例可以每秒打印一下当前时间
ScheduleTask.java

/**
 * @author yaLong
 * @date 2022/1/13
 */
@Component
@Slf4j
public class ScheduleTask {
    @Scheduled(cron = "*/1 * * * * ?")
    public void test(){
        System.out.println(System.currentTimeMillis());
    }
}

问题

当一个类中同时使用@RefreshScope和@Scheduled时,定时任务失效,原因如下

  • 需要动态刷新的类标注@RefreshScope注解
  • @RefreshScope 注解标注了@Scope 注解,并默认了ScopedProxyMode.TARGET_CLASS属性,此属性的功能就是在创建一个代理,在每次调用的时候都用它来调用GenericScope get 方法来获取对象.
  • 当改属性发生变更时,ContextRefresher refresh() ->RefreshScope refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 -> GenericScope 真正的 清理方法destroy() 实现清理缓存
  • 在下一次使用对象的时候,会调用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法创建一个新的对象,并存入缓存中,此时新对象因为Spring 的装配机制就是新的属性了
  • 调用了destroy()后,定时任务的缓存被清理了,又没有去生成这个对象,所以定时功能失效

解决方案

1.RefreshScopeRefreshedEvent,在收到消息之后从容器里获取一次当前bean,触发定时任务对象创建,就可以继续定时任务
test.java

/**
 * @author yaLong
 * @date 2022/2/12
 */
@EnableScheduling
@Configuration
@RefreshScope
public class test implements ApplicationListener<RefreshScopeRefreshedEvent> {
    @Value("${auto-task.start:false}")
    private Boolean startTask;

    @Scheduled(cron = "*/1 * * * * ?")
    public void test(){
        System.out.println(startTask+":"+System.currentTimeMillis());
    }

    @Override
    public void onApplicationEvent(RefreshScopeRefreshedEvent event) {

    }
}

2.配置和定时任务放在不同class
因为destroy()卸载的是当前类的实例,所以定时任务和配置分开写就可以
TaskConfig.java

/**
 * @author yaLong
 * @date 2022/2/12
 */
@RefreshScope
@Configuration
public class TaskConfig {
    /**
     * 自动任务调度的开关
     */
    @Value("${auto-task.start:false}")
    private Boolean startTask;

    public Boolean getStartTask() {
        return startTask;
    }

    public void setStartTask(Boolean startTask) {
        this.startTask = startTask;
    }

}

test.java

/**
 * @author yaLong
 * @date 2022/2/12
 */
@EnableScheduling
@Configuration
public class test{
    @Autowired
    private TaskConfig config;

    @Scheduled(cron = "*/1 * * * * ?")
    public void test(){
        System.out.println(config.getStartTask()+":"+System.currentTimeMillis());
    }
}

总结

  • 当改属性发生变更时,ContextRefresher refresh() ->RefreshScope refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 -> GenericScope 真正的 清理方法destroy() 实现清理缓存
  • 此时缓存被清除干净
  • 在下一次使用对象的时候,会调用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法创建一个新的对象,并存入缓存中,此时新对象因为Spring 的装配机制就是新的属性
  • 配置刷新后会卸载类实例,并重新实例化类(如果类中存在计数等情况需要注意)
  • @RefreshScope 不能修饰在 @Scheduled、listener、Timmer等类中
posted @ 2022-02-12 15:02  rm-rf*  阅读(423)  评论(0编辑  收藏  举报