我最近致力于重构一个处理客户端数据包的系统。系统执行一系列步骤,每个步骤消耗来自先前步骤(有时是内存中数据)的文件,并以文件或数据的形式产生其自己的输出。有时,特定步骤的输出数据已经可用。我必须小心确保当一步失败时,我们继续运行所有可能的步骤(不依赖于失败的步骤),以便最终输出尽可能完整。此外,并非所有步骤都必须在所有情况下运行。
以前,关系都隐含在代码结构中。例如:
void processClientData() {
try {
processA();
} catch(Exception e) {
log.log(Level.SEVERE, "exception occured in step A", e);
processC(); // C doesn't depend on A, so we can still run it.
throw e;
}
processB();
processC();
//etc... for ~20 steps
}
我通过引入任务来改变它以使依赖关系显式化,错误处理统一等等:
public interface Task {
List<Task> getDependencies();
void execute(); //only called after all dependencies have been executed
}
public class TaskRunner {
public void run(Set<Task> targets) {
// run the dependencies and targets ala ANT
// make sure to run all possible tasks on the "road" to targets
// ...
}
}
这开始感觉很像一个带有依赖关系管理的构建系统的非常淡化的版本(ANT,对我来说最熟悉)。我不想为这种事情引入ANT,我当然不想写出XML。
我的系统启动并运行(主要是),但它仍然感觉有点被黑客攻击,我已经反思了我多么厌恶重新发明轮子。我希望这是一个相当普遍的问题 - 一个比我聪明的人多次解决的问题。唉,几个小时的谷歌搜索什么都没发现
是否有一个实现此类事物的库,而不是一个非常重量级的构建系统?我也很欣赏任何指针,包括我应该从中获取灵感的其他语言(甚至是新系统)中的库。
编辑:我很欣赏这些建议(我会给予他们适当的考虑),但我真的不是在寻找一个“构建系统”本身。我正在寻找的东西更像是构建系统的内核,我可以直接从Java调用并用作一个小的,低开销的库来执行所述依赖性分析,任务执行,并由此产生资源管理。就像我说的,我有纯Java中的现有(工作)代码,我不想引入XML及其附带的所有包袱,没有非常令人信服的理由。答案 0 :(得分:2)
构建系统的核心是做三件事。它管理依赖,它测试某些东西是否“构建”,并“构建”未构建的东西。
依赖管理只不过是一种简单的拓扑排序。其余的是依赖顺序迭代任务,并处理它们。
您可以轻松创建类似的内容:
BuildSystem bs = new BuildSystem();
bs.addTask(new Task1());
bs.addTask(new Task...);
bs.addTask(new TaskN());
bs.build();
public void build() {
List<Task> sortedTasks = topologicalTaskSort(tasks);
for(Task t : sortedTasks) {
if (t.needsBuilding()) {
t.execute();
}
}
}
如果您不需要外部化任务列表,那么就没有理由使用XML文件或其他任何内容。
拓扑排序允许您简单地将任务添加到列表中,让系统对事物进行排序。 4个任务不是问题,更多是几十个任务的问题。
如果检测到依赖关系循环,则排序失败,这样就可以获得该控制。
这样的事情“太简单”,不需要框架。我现在不知道你是如何进行依赖管理的。
答案 1 :(得分:1)
我会考虑编写一个Maven插件,它不是那么难以及更轻的重量因为你只需要提供相关的特殊逻辑。所有基础设施都由Maven提供。 Maven 3甚至会为你提供类似并行构建的东西,你的插件可以免费支持它,以及它提供的所有其他东西。
Maven 3的主要目标之一是重写,以便在您自己的项目中嵌入工作流引擎尽可能轻松。
答案 2 :(得分:1)
看看jsr166 fork / join框架。在我看来,这正是你想要完成的事情。
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinTask.html
这包含在JDK7中,但作为5和6的单独jar可用。如果我不在我的平板电脑上,我会写一个更全面的例子。也许其他人可以在此期间扩张。
public class DependencyTreeTask extends RecursiveAction {
private final List<DependencyTreeTask> dependencies = new ArrayList<Task>();
public void addDependency(DependencyTreeTask t) { dependencies.add(t) }
public void compute() {
invokeAll(dependencies);
}
}
...
// build tree...
DependencyTreeTask root = ...
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(root);
如果您的图表未连接,您还必须注意,但有一套众所周知的算法可用于确定这一点。
答案 3 :(得分:0)
除了它的主要“构建系统”角色之外,我听说詹金斯被用于此类事情。我刚刚开始使用Jenkins,所以不能确定它是否能满足您的需求。到目前为止,我对它印象深刻。它使用起来相对简单,并且有很多配置选项。它也有很多插件。只需运行Jenkins并转到插件页面查看列表并安装它们。