简体   繁体   中英

How to autowire Application context in a ScheduledFutures runnable task

I am trying to create a module for running report tasks based on configuration. The thought is to pass report configuration to a single task for defining each report specifics. Report configuration contains data information and class names that will be invoked to do the separate tasks. For this I have a Scheduling Service for dynamically configuring on startup my scheduled tasks. Task (runnable impl) is ReportExecutor.

@Service
public class ReportSchedulingService {
    private static final Logger logger = LoggerFactory.getLogger(ReportSchedulingService.class);

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    @Autowired
    private ReportList reportList;

    private static Map<String, ScheduledFuture<?>> jobsMap = new HashMap<String, ScheduledFuture<?>>();

    public void scheduleAllReports() {
        for (Map.Entry<String, Report> reportEntry : reportList.getReports().entrySet()) {
            String reportName = reportEntry.getKey();
            Report report = reportEntry.getValue();
            String jobId = UUID.randomUUID().toString();
            logger.info(String.format("Scheduling report [%s] with job id: [%s] and cron expression: [%s]",
                reportName, jobId, report.getCron()));
            ReportExecutor execution = new ReportExecutor();
            report.setId(jobId);
            report.setName(reportName);
            execution.setTaskDefinition(report);
            ScheduledFuture<?> scheduledTask = taskScheduler.schedule(execution,
                new CronTrigger(report.getCron(), TimeZone.getTimeZone(TimeZone.getDefault().getID())));
            getJobMap().put(reportName + "_" + jobId, scheduledTask);
        }
    }

    private synchronized Map<String, ScheduledFuture<?>> getJobMap() {
        if (jobsMap == null) {
            jobsMap = new HashMap<String, ScheduledFuture<?>>();
        }
        return jobsMap;
    }

    public void removeScheduledTask(String jobId) {
        ScheduledFuture<?> scheduledTask = getJobMap().get(jobId);
        if (scheduledTask != null) {
            scheduledTask.cancel(true);
            getJobMap().put(jobId, null);
        }
    }
}

Report Executor is:

@Component
public class ReportExecutor implements Runnable {

    @Autowired
    private ApplicationContext appContext;

    private Report reportDefinition;

    @Override
    public void run() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        System.out.println(reportDefinition);
        IReportGenerator generator = appContext.getBean(
            reportDefinition.getExecute(),
            IReportGenerator.class);
        JSONArray reportData = generator.generate(reportDefinition);

        IReportNotifier notifier =
            appContext.getBean(reportDefinition.getNotification().getExecute(), IReportNotifier.class);
        notifier.send(reportDefinition, reportData);

    }
    public Report getTaskDefinition() {
        return reportDefinition;
    }
    public void setTaskDefinition(Report reportDefinition) {
        this.reportDefinition = reportDefinition;
    }
}

But I have also tried with:

@Component
public class ReportExecutor implements Runnable, ApplicationContextAware {

    private ApplicationContext appContext;

    private Report reportDefinition;

    @Override
    public void run() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        System.out.println(reportDefinition);
        IReportGenerator generator = appContext.getBean(
            reportDefinition.getExecute(),
            IReportGenerator.class);
        JSONArray reportData = generator.generate(reportDefinition);

        IReportNotifier notifier =
            appContext.getBean(reportDefinition.getNotification().getExecute(), IReportNotifier.class);
        notifier.send(reportDefinition, reportData);

    }

    public Report getTaskDefinition() {
        return reportDefinition;
    }

    public void setTaskDefinition(Report reportDefinition) {
        this.reportDefinition = reportDefinition;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.appContext = applicationContext;
    }

}

Seems that I cannot get the application context here and end up to a NPE at:

        IReportGenerator generator = appContext.getBean(

The stackTrace is:

java.lang.NullPointerException: null
    at ote.itarc.report.ReportExecutor.run(ReportExecutor.java:27) ~[classes/:?]
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93) [spring-context-5.2.10.RELEASE.jar:5.2.10.RELEASE]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
    at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
    at java.lang.Thread.run(Thread.java:836) [?:?]

ReportList object comes from yaml properties:

reportlist:
  reports:
    missinginfra:
      desc: "This report provides ..."
      execute: QueryReportGenerator
      query: SELECT * FROM DUAL
      cron: "0 0/2 * 1/1 * ?"
      notification:
        execute: EmailCsvReportNotifier
        templateid: 0

With the following definition

@ConfigurationProperties(prefix = "reportlist")
@Configuration
@EnableConfigurationProperties
public class ReportList {

    private Map<String, Report> reports;

    public Map<String, Report> getReports() {
        return reports;
    }

    public void setReports(Map<String, Report> reports) {
        this.reports = reports;
    }

    @Override
    public String toString() {
        return "ReportList [reports=" + reports + "]";
    }
}

Make the ReportExecutor a Spring managed bean and make it prototype scoped. Next in your ReportSchedulingService use the ApplicationContext to get an instance (and let Spring do the wiring) and schedule it.

@Service
public class ReportSchedulingService {
    private static final Logger logger = LoggerFactory.getLogger(ReportSchedulingService.class);

    private final Map<String, ScheduledFuture<?>> jobsMap = new ConcurrentHashMap<String, ScheduledFuture<?>>();

    private final TaskScheduler taskScheduler;
    private final ReportList reportList;
    private final ApplicationContext ctx;

    public ReportSchedulingService(ReportList reportList, TaskScheduler taskScheduler, ApplicationContext ctx) {
        this.reportList=reportList;
        this.taskScheduler=taskScheduler;
        this.ctx=ctx;
    }

    public void scheduleAllReports() {
        for (Map.Entry<String, Report> reportEntry : reportList.getReports().entrySet()) {
            String reportName = reportEntry.getKey();
            Report report = reportEntry.getValue();
            String jobId = UUID.randomUUID().toString();
            logger.info(String.format("Scheduling report [%s] with job id: [%s] and cron expression: [%s]",
                reportName, jobId, report.getCron()));
            ReportExecutor execution = ctx.getBean(ReportExecutor.class);
            report.setId(jobId);
            report.setName(reportName);
            execution.setTaskDefinition(report);
            ScheduledFuture<?> scheduledTask = taskScheduler.schedule(execution,
                new CronTrigger(report.getCron(), TimeZone.getTimeZone(TimeZone.getDefault().getID())));
            this.jobsMap.put(reportName + "_" + jobId, scheduledTask);
        }
    }

    public void removeScheduledTask(String jobId) {
        ScheduledFuture<?> scheduledTask = this.jobsMap.get(jobId);
        if (scheduledTask != null) {
            scheduledTask.cancel(true);
            getJobMap().put(jobId, null);
        }
    }
}

NOTE: I took the liberty to improve your code a little as well, using constructor injection and using a ConcurrentHashMap instead of syncronized .

@Component
@Scope(scopeName=SCOPE_PROTOTYPE, proxyMode=TARGET_CLASS)
public class ReportExecutor implements Runnable {

    private final ApplicationContext appContext;
    private Report reportDefinition;

    public ReportExecutor(ApplicationContext ctx) {
      this.appContext=ctx;
    }

    @Override
    public void run() {
        IReportGenerator generator = appContext.getBean(
            reportDefinition.getExecute(),
            IReportGenerator.class);
        JSONArray reportData = generator.generate(reportDefinition);

        IReportNotifier notifier =
            appContext.getBean(reportDefinition.getNotification().getExecute(), IReportNotifier.class);
        notifier.send(reportDefinition, reportData);

    }

    public void setTaskDefinition(Report reportDefinition) {
        this.reportDefinition = reportDefinition;
    }
}

The scoped proxy will create a new instance for each call to getBean . So you get a fresh, clean instance for each execution you want to schedule.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM