尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java_Pipeline 实战:从零搭建可插拔的批处理管道
简介这是一份面向Java开发者与DevOps初学者的Jenkins Pipeline实战项目包围绕持续集成与持续部署CI/CD流程展开帮助读者理解如何用流水线方式自动化构建、测试与部署Java应用。压缩包共10个文件约222KB以java源码、jar依赖库、xml构建配置为主另含Jenkinsfile、gitignore与md说明文档覆盖从代码编译到打包部署的完整链路。项目通过Jenkinsfile定义Groovy DSL流水线配合build.xml与Ant完成编译、单元测试与JAR打包并借助junit等jar实现测试自动化清晰呈现stage-step结构、版本控制触发与并行化构建等核心机制。已有304人学习下载适合希望掌握Jenkins Pipeline编写、理解CI/CD阶段划分与构建工具集成的开发者参考可据此快速搭建可运行的Java管道示例学习流水线脚本组织方式与自动化部署思路。1. 从一堆散装脚本到一条流水线Java_Pipeline 到底在解决什么如果你手头有好几个 Java 小工具——一个读文件、一个做转换、一个写数据库——每次跑都要手动按顺序点一遍中间某步挂了还得从头再来那 Java_Pipeline 就是冲这个场景来的。它把「读入 → 处理 → 输出」拆成可插拔的 Stage用一条 Pipeline 串起来支持顺序执行、异常中断和阶段级日志。适合谁适合手里有零散批处理逻辑、想快速收拢成一条可维护链路的开发者也适合想学管道模式落地写法的同学。它不依赖重型框架纯 Java 实现拿过来改 Stage 就能接自己的业务。下面从目录结构、核心抽象、运行方式到二次开发一层层拆开讲。2. 工程结构与核心抽象Pipeline、Stage、Context 三件套2.1 目录布局与模块职责拿到源码先别急着跑花两分钟看目录后面改代码能少走弯路。这个项目的结构是典型的「接口 实现 启动器」三层没有多余的脚手架Java_Pipeline/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── pipeline/ │ │ │ ├── core/ │ │ │ │ ├── Pipeline.java # 管道调度器 │ │ │ │ ├── Stage.java # 阶段接口 │ │ │ │ ├── PipelineContext.java # 上下文载体 │ │ │ │ └── StageResult.java # 阶段返回结果 │ │ │ ├── stages/ │ │ │ │ ├── ReadStage.java # 读取阶段 │ │ │ │ ├── TransformStage.java # 转换阶段 │ │ │ │ └── WriteStage.java # 写出阶段 │ │ │ ├── exception/ │ │ │ │ └── StageException.java # 阶段异常 │ │ │ └── App.java # 入口 │ │ └── resources/ │ │ └── pipeline.properties # 阶段配置 │ └── test/ │ └── java/pipeline/PipelineTest.java ├── pom.xml └── README.mdcore放抽象和调度stages放具体实现App负责组装。这种分法好处是你加新阶段只动stages调度逻辑一行不用碰。常见做法是把配置也外置到resources改阶段顺序不用重新编译。2.2 Stage 接口所有处理逻辑的统一入口整个项目最核心的就是Stage这个接口它定义了「一个阶段该长什么样」。先看代码package pipeline.core; public interface Stage { /** * 阶段名称用于日志和配置引用 */ String getName(); /** * 执行阶段逻辑 * param context 管道上下文携带输入数据和中间结果 * return 阶段执行结果含状态和输出 * throws StageException 阶段内部异常统一抛出 */ StageResult execute(PipelineContext context) throws StageException; }逻辑说明接口只暴露两个方法。getName()让调度器能在日志里标出「现在跑到哪个阶段了」排查问题时非常关键execute()是唯一执行入口输入输出都通过PipelineContext传递阶段之间不直接耦合。参数说明context是贯穿整条管道的共享载体读阶段往里塞原始数据转换阶段从里面取数据再覆盖写阶段最后消费。StageResult里通常包含success布尔值和message调度器靠它决定继续还是中断。我一般会在这个接口上加一个default int order()返回排序值这样阶段顺序可以靠注解或配置决定而不是硬编码在App里。原项目没写但这是二次开发第一个值得加的点。2.3 PipelineContext阶段之间的数据总线阶段之间怎么传数据答案全在PipelineContext。它本质是一个带类型的 Map 包装package pipeline.core; import java.util.HashMap; import java.util.Map; public class PipelineContext { private final MapString, Object data new HashMap(); public void put(String key, Object value) { data.put(key, value); } SuppressWarnings(unchecked) public T T get(String key, ClassT type) { Object value data.get(key); if (value null) { return null; } if (!type.isInstance(value)) { throw new IllegalArgumentException( key key 期望类型 type.getName() 实际类型 value.getClass().getName()); } return (T) value; } public boolean contains(String key) { return data.containsKey(key); } }逻辑说明put存数据get带类型校验取数据。类型不匹配直接抛IllegalArgumentException而不是返回 null 让你在后面某行莫名其妙空指针——这是这个类最值钱的地方。参数说明key建议用常量定义比如ContextKeys.INPUT_LINES避免各阶段拼字符串拼错。type传目标类型的Class对象编译期就能约束。提示如果你要传大数据集别往 Context 里塞几百万行 List内存会炸。常见做法是 Context 只存文件路径或迭代器阶段内部自己流式读。2.4 Pipeline 调度器顺序执行与异常中断调度器负责按顺序跑阶段遇到异常就停。核心逻辑不复杂但异常处理这块值得细看package pipeline.core; import java.util.ArrayList; import java.util.List; public class Pipeline { private final ListStage stages new ArrayList(); private final PipelineContext context new PipelineContext(); public Pipeline addStage(Stage stage) { stages.add(stage); return this; } public PipelineContext run() { for (Stage stage : stages) { long start System.currentTimeMillis(); try { StageResult result stage.execute(context); long cost System.currentTimeMillis() - start; System.out.printf([Pipeline] 阶段%s 状态%s 耗时%dms%n, stage.getName(), result.getStatus(), cost); if (!result.isSuccess()) { throw new StageException( 阶段 stage.getName() 返回失败: result.getMessage()); } } catch (StageException e) { System.err.printf([Pipeline] 阶段%s 执行异常管道终止%n, stage.getName()); throw e; } } return context; } }逻辑说明addStage返回this支持链式调用组装管道时一行到底。run()里每个阶段记录耗时并打印失败就抛StageException终止后续阶段——这是「快速失败」策略避免脏数据继续往下流。参数说明stages用ArrayList保序别换成HashSet否则执行顺序就玄学了。context在构造时创建整条管道共用一个实例。常见做法是在run()外面包一层重试但重试要小心读阶段重试没问题写阶段重试可能重复写。我一般只对读和转换阶段开重试写阶段靠幂等键兜底。3. 跑起来从零到第一条管道输出3.1 环境准备与依赖确认这个项目是标准 Maven 工程依赖极简。先确认本机环境java -version # 需要 JDK 8 及以上 mvn -version # 需要 Maven 3.6pom.xml里通常只声明junit做测试没有 Spring、没有 Lombok。如果你本地 Maven 仓库是空的第一次mvn compile会下载插件耐心等一两分钟。网络受限的环境可以配阿里云镜像改settings.xml的mirror段即可。注意JDK 版本别用太新的预览版pom.xml里maven.compiler.source一般写的是 1.8用 JDK 17 编译时如果没配release可能出现字节码版本警告。稳妥做法是本地装个 JDK 8 或 11。3.2 编译与运行入口环境没问题后两条命令跑起来# 编译并跑测试 mvn clean test # 打包后直接运行入口类 mvn clean package java -cp target/classes pipeline.App逻辑说明mvn clean test会先编译再跑PipelineTest如果测试里断言了阶段顺序和输出内容这一步能帮你确认代码没被改坏。package生成 jar 后用java -cp指定 classpath 运行App比java -jar更灵活因为App可能依赖resources下的配置文件。参数说明-cp target/classes把编译输出目录加进类路径如果App要读pipeline.properties确保target/classes下有这个文件Maven 默认会把resources拷过去。3.3 配置阶段顺序与参数pipeline.properties控制阶段启用和顺序典型内容长这样pipeline.stagesreadStage,transformStage,writeStage readStage.sourcedata/input.txt transformStage.ruleuppercase writeStage.targetdata/output.txt逻辑说明pipeline.stages用逗号分隔阶段名App读取后按名字从工厂里取对应Stage实例依次addStage。每个阶段自己的参数用「阶段名.参数名」前缀避免不同阶段参数撞名。参数说明readStage.source是输入文件路径相对路径以工作目录为基准transformStage.rule决定转换逻辑常见值有uppercase、trim、replacewriteStage.target是输出路径目录不存在时WriteStage里一般会mkdirs。我一般会在App里加一段校验读配置后检查每个阶段名是否在工厂注册表里不在就启动时直接报错而不是跑到一半才发现某个阶段是 null。这个改动不到十行能省很多排查时间。3.4 验证输出与日志解读跑完后看控制台和输出文件[Pipeline] 阶段readStage 状态SUCCESS 耗时12ms [Pipeline] 阶段transformStage 状态SUCCESS 耗时3ms [Pipeline] 阶段writeStage 状态SUCCESS 耗时8ms逻辑说明每行对应一个阶段状态为SUCCESS说明该阶段正常返回。如果某行没出现说明管道在那之前就中断了往上找第一个报错的阶段名。参数说明耗时单位毫秒读阶段通常最慢IO 密集转换阶段最快。如果转换阶段耗时异常高检查是不是在循环里反复context.get做了类型转换——把结果提到循环外能明显改善。输出文件data/output.txt内容应该和输入一致如果ruleuppercase则全大写。对不上就回到TransformStage看转换逻辑别怀疑调度器。4. 避坑与常见问题排查4.1 阶段返回 null 导致空指针现象跑到某个阶段抛NullPointerException堆栈指向Pipeline.run()里result.getStatus()那行。原因自定义Stage的execute方法忘了return或者某个分支直接return null。解决在Pipeline.run()里加Objects.requireNonNull(result, 阶段 stage.getName() 返回 null)把问题定位到具体阶段而不是调度器。同时约定所有Stage实现必须返回非 null 的StageResult。4.2 配置文件路径找不到现象启动报FileNotFoundException提示pipeline.properties不存在。原因用java -cp target/classes运行时工作目录是项目根目录而App里用getClass().getResourceAsStream(/pipeline.properties)读的是 classpath 根两者路径基准不同。解决统一用 classpath 方式读配置确保mvn package后target/classes下有该文件。如果坚持用文件路径读就在App里打印new File(.).getAbsolutePath()先确认工作目录到底在哪。4.3 阶段顺序与配置不一致现象明明配置里writeStage在最后日志里却先跑了写阶段。原因App组装时没按pipeline.stages顺序addStage而是用了HashMap遍历阶段工厂顺序随机。解决工厂返回阶段时用LinkedHashMap或直接按配置里的名字列表逐个取。顺序敏感的场景永远别依赖HashMap的遍历顺序这是血泪经验。4.4 大文件导致内存溢出现象处理几百 MB 的输入文件时抛OutOfMemoryError。原因ReadStage用Files.readAllLines一次性把整个文件读进 List再塞进 Context。解决改成流式读取Context 里只存BufferedReader或文件路径TransformStage逐行处理逐行传给WriteStage。如果阶段间必须传全量数据考虑用临时文件做中转而不是全放内存。4.5 异常被吞掉导致静默失败现象管道跑完了但输出文件是空的日志里没有任何报错。原因某个Stage的execute里用了try-catch捕获异常后只打了e.printStackTrace()却没重新抛出Pipeline以为阶段成功了。解决约定Stage内部要么不捕获要么捕获后包装成StageException抛出。printStackTrace只适合本地调试生产代码里必须让异常向上传播否则管道就成了黑匣子。5. 二次开发加一个 Stage 并接入真实业务5.1 新增阶段的完整步骤假设你要加一个「过滤空行」的阶段叫FilterStage。第一步在stages包下新建类package pipeline.stages; import pipeline.core.*; import java.util.List; import java.util.stream.Collectors; public class FilterStage implements Stage { Override public String getName() { return filterStage; } Override public StageResult execute(PipelineContext context) throws StageException { // 从上下文取出上一阶段写入的行列表 ListString lines context.get(lines, List.class); if (lines null) { throw new StageException(filterStage 缺少输入: lines); } // 过滤掉空白行 ListString filtered lines.stream() .filter(line - line ! null !line.trim().isEmpty()) .collect(Collectors.toList()); // 覆盖写回供后续阶段消费 context.put(lines, filtered); return StageResult.success(过滤后剩余 filtered.size() 行); } }逻辑说明getName返回的名字要和配置文件里写的一致。execute先从 Context 取lines取不到就抛StageException并说明缺什么——这比空指针友好得多。过滤用 Stream 一行搞定结果覆盖回同一个 key后续阶段无感知。参数说明lines这个 key 建议抽成常量类比如ContextKeys.LINES避免各阶段拼错。StageResult.success是静态工厂方法原项目里如果没有就自己加一个比new StageResult(true, msg)可读。第二步在App的阶段工厂里注册private static MapString, SupplierStage stageFactory() { MapString, SupplierStage factory new LinkedHashMap(); factory.put(readStage, ReadStage::new); factory.put(transformStage, TransformStage::new); factory.put(filterStage, FilterStage::new); // 新增 factory.put(writeStage, WriteStage::new); return factory; }逻辑说明用LinkedHashMap保序Supplier延迟创建实例。注册后配置文件里写pipeline.stagesreadStage,transformStage,filterStage,writeStage就能生效。参数说明SupplierStage的好处是阶段实例在真正需要时才 new如果某个阶段构造很重比如要连数据库不会在启动时全部初始化。5.2 改造验证怎么确认新阶段真的生效加完代码别急着上生产用三步验证验证项操作预期结果编译通过mvn clean compileBUILD SUCCESS单元测试在PipelineTest加一个testFilterStage空行被过滤非空行保留端到端造一个含空行的输入文件跑App输出文件无空行日志出现filterStage单元测试里重点断言两件事一是filterStage执行后 Context 里lines的数量二是writeStage拿到的数据确实是过滤后的。常见做法是用一个内存中的List直接构造 Context不碰文件 IO测试跑得快且稳定。5.3 一个容易忽略的技巧阶段幂等与重跑管道跑到一半挂了你想从失败阶段重跑而不是从头再来。这需要每个阶段幂等。我的习惯是给Stage接口加一个default boolean isIdempotent() { return false; }读和转换阶段返回true写阶段返回false。调度器在重试时只重跑幂等阶段写阶段失败就人工介入。另一个技巧是给 Context 加一个snapshot()方法在每个阶段执行前把当前数据浅拷贝一份存到List里。失败时能回滚到上一个阶段的快照相当于给管道加了后悔药。代价是内存翻倍只适合数据量不大的场景。从那以后我每次加新 Stage都强制走一遍「编译 → 单测 → 端到端」三步绝不跳过单测直接跑主流程。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

程序员转大模型:从薪资热潮到RAG与Agent落地全攻略

程序员转大模型:从薪资热潮到RAG与Agent落地全攻略

2025年下半年的热搜词条里,最扎眼的一条就是“大模型岗位薪资暴涨40%”,再配上“年薪百万轻轻松松”,搞得不少程序员朋友直接在群里问:现在换赛道还来得及吗?作为一个从传统后端转到机器学习,再一路做到大模…

📅 2026/10/21 9:58:30
OM文件格式逆向解析:从二进制到计算图的完整工程实践

OM文件格式逆向解析:从二进制到计算图的完整工程实践

“OM文件格式逆向解析”这个方向,我接到的需求是搞清楚一类离线模型文件到底是怎么从一串不懂含义的二进制字节流,还原成一张算子级别的计算图。做这行的都知道,拿起一份陌生的模型产物,先不急着跑推理,先把自己当成一…

📅 2026/10/21 9:48:03
Agent技能组件化实战:构建可插拔技能包与调度系统

Agent技能组件化实战:构建可插拔技能包与调度系统

1. 项目概述:agent-skills 到底是什么,适合谁用“agent-skills”看着像个不起眼的技术名词,但真正做智能体(Agent)开发的人一眼就能嗅到它的分量。简单说,这是一个围绕“Agent 技能组件化”而设计的项目&am…

📅 2026/10/21 9:48:03
MORE NEWS

更多资讯

📰

【信息科学与工程学】计算机科学与自动化——第二十四篇 编译器04 LLVM编译器01

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

150KHz 3A PWM 降压型 DC/DC 转换器D1507

在现代电子设备中,电源管理芯片的性能和设计简便性至关重要。D1507 系列降压型 DC/DC 转换器,以其高效率、宽输入电压范围以及简洁的设计,为工程师提供了一个卓越的电源解决方案。产品简介D1507 是一款专为降压(Buck)D…

📰

IEEE Transactions on Communications (TCOM) 投稿状态记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

软考 系统架构设计师历年真题集萃(109)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

双运算放大器D4558

在现代电子系统中,运算放大器作为信号处理的核心元件,其性能直接影响到整个系统的稳定性和精度。D4558 双运算放大器,凭借其卓越的性能和广泛的应用适配性,为工程师提供了可靠的信号处理解决方案。产品简介D4558 是一款由两个高性…

📰

Qwen-Image-2.1-PE-T2I输出格式全解析:如何正确读取rewritten_prompt与wh_ratio

【免费下载链接】Qwen-Image-2.1-PE-T2I 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen-Image-2.1-PE-T2I 点击查看 免费下载 Qwen-Image-2.1-PE-T2I 是一个开源的文生图提示词改写模型(Prompt Rewriting Model):输入一…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬