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 都是自己编写的用于计算的条件对象,看名字应该知道它们是用来干什么的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值