spring定时任务之cronJob

情景:
  数据库中的数据需要根据formula在一定时间上计算得到相应的结果数据,就是说,根据formula去计算,得到相应结果保存在相应字段上,这个job是定时触发的,计算按照一定的事件类型。


代码:
public class DashboardDataCalculationJob {
    private final static Logger log = LoggerFactory.getLogger(DashboardDataCalculationJob.class);

    protected String cronJobKey;

    private DashboardDataCalculationService dashboardDataCalculationService;

    public void setCronJobKey(final String cronJobKey) {
        this.cronJobKey = cronJobKey;
    }

    public void setDashboardDataCalculationService(
            final DashboardDataCalculationService dashboardDataCalculationService) {
        this.dashboardDataCalculationService = dashboardDataCalculationService;
    }

    public void execute() {

        long start = System.currentTimeMillis();

        Date date = new Date();

        log.info("DashboardDataCalculationJob executing Daily...");
        boolean success = dashboardDataCalculationService.calculate(cronJobKey, PeriodType.Daily,
                date);

        if (success) {
            log.info("DashboardDataCalculationJob executing Monthly...");
            success = dashboardDataCalculationService.calculate(cronJobKey, PeriodType.Monthly,
                    date);
        }

        if (success) {
            log.info("DashboardDataCalculationJob executing Yearly...");
            dashboardDataCalculationService.calculate(cronJobKey, PeriodType.Yearly, date);
        }

        log.info("DashboardDataCalculationJob took {} ms.", System.currentTimeMillis() - start);
    }
}





这个类负责调用正确的cronJob去执行定时任务。我们可以看到,传过来相应的cronJobKey,首先从daily开始计算,只要成功,就会触发一系列的计算

接下来,我们看看DashboardDataCalculationService这个接口.
@WebService
public interface DashboardDataCalculationService {

    public boolean calculate(String cronJobKey, PeriodType periodType, Date date);

    public boolean calculateFields(List<String> fieldList, PeriodType periodType, Date date);

    public Double doCalculate(String formula, Date calculateDate, PeriodType periodType);
}


这个接口中定义了三个calculate方法,用于计算,再看看它的实现类中
public boolean calculate(String cronJobKey, PeriodType periodType, Date date);方法

public boolean calculate(final String cronJobKey, final PeriodType periodType, final Date date) {

        try {
            init();

            Date calculateDate = JobUtil.getCalculateDate(periodType, date);

            SystemCronJob systemCronJob = systemCronJobService.getSystemCronJobByJobKey(cronJobKey);

            if (systemCronJob == null) {
                log.error("SystemCronJob not found by cronJobKey: {}", cronJobKey);
                String subject = "SystemCronJob not found by cronJobKey: " + cronJobKey;
                String content = subject;
                try {
                    emailService.sendEmail(subject, content);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                return false;
            }

            List<DashboardDataDefinition> defList = systemCronJob.getDashboardDataDefinitions();

            for (DashboardDataDefinition def : defList) {
                calculateDef(periodType, calculateDate, def);
            }

            return true;

        } catch (Exception ex) {
            log.error(ex.getMessage());
            String subject = "DashboardDataCalculationService Failed: " + ex.getMessage();
            StringWriter writer = new StringWriter();
            ex.printStackTrace(new PrintWriter(writer));
            String content = writer.toString();
            try {
                emailService.sendEmail(subject, content);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            return false;
        } finally {
            release();
        }
    }



看下这个方法,init()方法初始化了ThreadLocal变量中的参数,
 private static final ThreadLocal<Map<String, Double>> cachedValue = new ThreadLocal<Map<String, Double>>();
    private static final ThreadLocal<Map<String, Boolean>> cachedValueSumLast = new ThreadLocal<Map<String, Boolean>>();



public void init() {
        cachedValue.set(new HashMap<String, Double>());
        cachedValueSumLast.set(new HashMap<String, Boolean>());
    }



接下来,systemCronJobService 负责去数据库拿到相应的SystemCronJob 对象,如果没拿到,则直跳出返回。
拿到SystemCronJob 对象后,通过它可以得到相应的需要计算的数据表对象,得到要计算的记录,分条执行calculateDef(periodType, calculateDate, def)。

  private void calculateDef(final PeriodType periodType, final Date calculateDate,
            final DashboardDataDefinition def) {
        log.info("ChartField: {}, FormulaText: {}", def.getChartField(), def.getFormulaText());

        String formula = def.getFormulaText();

        if (StringUtils.isBlank(formula)) {
            log.warn("'{}' FormulaText is blank", def.getChartField());
            return;
        }

        log.info("formula: {}={}", def.getChartField(), formula);
        Double value = doCalculate(formula, calculateDate, periodType);



在方法public boolean calculate(final String cronJobKey, final PeriodType periodType, final Date date) 调用 private void calculateDef(final PeriodType periodType, final Date calculateDate,
            final DashboardDataDefinition def)方法,在此方法中,最终会调用
 Double value = doCalculate(formula, calculateDate, periodType);


doCalculate()方法,所以,关键的计算在这里面。

    public Double doCalculate(final String formula, final Date calculateDate,
            final PeriodType periodType) {

        String cacheKey = getCacheKey(formula, calculateDate, periodType);

        if (cachedValue.get() == null) {
            init();
        }

        if (cachedValue.get().containsKey(cacheKey)) {
            Double cached = cachedValue.get().get(cacheKey);
            Boolean sumLastCal = cachedValueSumLast.get().get(cacheKey);
            if (sumLastCal != null && sumLastCal.booleanValue() == true) {
                FormulaMethod.sumLastCal.set(Boolean.TRUE);
            }
            log.info("use cached value {} :{}", formula, cached);
            return cached;
        }

        long start = System.currentTimeMillis();

        log.info("calculateDate: {}", calculateDate);
        log.info("calculateDate.time: {}", calculateDate.getTime());
        log.info("periodType: {}", periodType);

        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(formula);

        StandardEvaluationContext context = new StandardEvaluationContext();
        DashboardDataAccessor accessor = new DashboardDataAccessor(calculateDate, periodType);

        context.setRootObject(new FormulaMethod(calculateDate, periodType, accessor));

        List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
        propertyAccessors.add(accessor);
        context.setPropertyAccessors(propertyAccessors);

        Object value = exp.getValue(context);
        log.info("{} = {}", formula, value);

        log.info("took {} ms.", System.currentTimeMillis() - start);

        double v = Double.parseDouble(value.toString());
        cachedValue.get().put(cacheKey, v);
        cachedValueSumLast.get().put(cacheKey, FormulaMethod.sumLastCal.get());
        return v;
    }



这个方法中,首先把要计算的条件组成一个key,放在本地线程当中,其中有一个相应的计算结果,如果下次计算的条件与其相同,则不需要计算了,直接拿出结果就OK了,这样就节省了很多时间。

关键的计算是调用了spring的ExpressionParser及 Expression,让ExpressionParser去解析我们的formula,得到Expression 对象,最后有Expression对象调用它的getValue()方法得到计算结果。

当然,其中用到StandardEvaluationContext,PropertyAccessor,这些都是计算不可少的spring自带对象, DashboardDataAccessor以及FormulaMethod 都是自己编写的用于计算的条件对象,看名字应该知道它们是用来干什么的。

你可能感兴趣的:(spring)