Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.9k views
in Technique[技术] by (71.8m points)

spring - @RefreshScope stops @Scheduled task

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I added @RefreshScope. But as soon as I update the config value on Consul, the fixedRate task stops running.

@Service
@RefreshScope
public class MonitorService {

    @Autowired
    private AppConfig appConfig;

    @PostConstruct
    public void postConstRun() {
        System.out.println(appConfig.getMonitorConfig());
    }

    @Scheduled(fixedRate = 1000)
    public void scheduledMonitorScan() {
        System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
    }
}

AppConfig class just has a single String parameter:

@Configuration
@Getter
@Setter
public class AppConfig {

    @Value("${monitor-config:default value}")
    private String monitorConfig;
}

As soon as I update the value in consul, the scheduled task just stops running (display in sheduledMonitorScan method) stop showing up.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's how we've solved this issue.

/**
 * Listener of Spring's lifecycle to revive Scheduler beans, when spring's
 * scope is refreshed.
 * <p>
 * Spring is able to restart beans, when we change their properties. Such a
 * beans marked with RefreshScope annotation. To make it work, spring creates
 * <b>lazy</b> proxies and push them instead of real object. The issue with
 * scope refresh is that right after refresh in order for such a lazy proxy
 * to be actually instantiated again someone has to call for any method of it.
 * <p>
 * It creates a tricky case with Schedulers, because there is no bean, which
 * directly call anything on any Scheduler. Scheduler lifecycle is to start
 * few threads upon instantiation and schedule tasks. No other bean needs
 * anything from them.
 * <p>
 * To overcome this, we had to create artificial method on Schedulers and call
 * them, when there is a scope refresh event. This actually instantiates.
 */
@RequiredArgsConstructor
public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
    private final List<RefreshScheduler> refreshSchedulers;

    @Override
    public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
        refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
    }
}

So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.

public interface RefreshScheduler {
    /**
     * Used after refresh context for scheduler bean initialization
     */
    default void materializeAfterRefresh() {
    }
}

And here is actual job, whose parameter from.properties can be refreshed.

public class AJob implements RefreshScheduler {
    @Scheduled(cron = "${from.properties}")
    public void aTask() {
        // do something useful
    }
}

UPDATED: Of course AJob bean must be marked with @RefreshScope in @Configuration

@Configuration
@EnableScheduling
public class SchedulingConfiguration {
    @Bean
    @RefreshScope
    public AJob aJob() {
        return new AJob();
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...