实现作业
在实现后台作业时,需将其逻辑与其触发方式及触发位置解耦。这确保了能够从不同来源灵活地触发该作业。
例如,您可能希望在每次应用程序启动时运行某个作业。在这种情况下,您可能希望在主线程中运行它,阻塞应用程序剩余部分的初始化,直到该作业完成为止。您也可能希望每天在后台线程中运行一次该作业——例如在午夜,或在发布某个应用事件时运行。
以下是一个具有三种不同触发方式的作业的可视化示例:
在代码中,作业是一个 Spring bean,并用 @Service 注解标记。它包含一个或多个方法,当在调用线程中调用该作业时执行。
下面是实现单一后台作业的 Spring bean 示例:
Source code
Java
import org.springframework.stereotype.Service;
@Service
public class MyBackgroundJob {
    public void performBackgroundJob() {
        ...
    }
}对于从同一包内 触发 的作业,该类可为包私有。需要由外部触发的作业必须为 public。
事务
操作数据库的作业应自行管理事务。由于作业为 Spring bean,您可以采用声明式或编程式事务管理。
下面是使用声明式事务管理确保作业在新事务内运行的后台作业示例:
Source code
Java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyBackgroundJob {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void performBackgroundJob() {
        ...
    }
}有关事务管理的更多信息,请参阅 事务 文档页面。
批处理作业
建议为批处理作业实现两个版本:一个处理所有适用输入;另一个处理特定输入集。这种方式在需要处理个别情况或从错误中恢复时提供了灵活性。
例如,用于为已发货订单生成发票的批处理作业可以如下所示:
Source code
Java
@Service
public class InvoiceCreationJob {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createInvoicesForOrders(Collection<OrderId> orders) {
        ...
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createInvoicesForAllApplicableOrders() {
        ...
    }
}本例中,第一个方法为作为参数传递的那些订单生成发票。第二个方法为所有已发货但尚未开具发票的订单生成发票。
从一开始就按此方式实现批处理作业并不会增加太多工作量,却能带来可能用得着的灵活性。以发票生成作业为例,假设您在生产环境中发现了一个 bug,导致某些订单在数据库中存在错误数据,因此批处理作业无法为这些订单生成发票。
修复 bug 很简单,但用户不希望等待下次批处理。因此,您可以在用户界面中添加一个按钮,让用户能够为单个订单触发发票生成。