告别996?用AI重构工作流后,效率暴涨
大家好,我是小悟。
一、背景与转变
在AI代码助手普及之前,我的Java编码流程典型是:理解需求 → 翻阅Java文档/Stack Overflow → 手写POJO/Service/Controller → 调试NullPointerException → 修改 → 重复。Spring框架的样板代码尤其耗费时间。
现在,AI彻底改变了这一切。我的角色从“代码的生产者”变成了“代码的架构师和审阅者”——我决定架构、接口设计、异常处理策略,AI负责生成实体类、Repository、Service骨架,我负责校验事务边界、线程安全、性能优化。
二、详细步骤
第一步:需求理解与拆解(人工主导)
做什么:拿到一个任务后,我先自己拆解成清晰的模块和子任务,定义好接口契约。
示例:假设要做一个“用户上传图片并自动添加水印”的功能。
我的拆解(Java技术栈视角):
- Controller层:
@PostMapping接收MultipartFile,参数校验 - Service层:业务逻辑——校验格式、调用水印服务、保存文件
- 水印处理组件:独立
WatermarkService,支持文字/图片水印 - 存储策略:本地文件系统或OSS(可配置)
- 异步处理:大图用
@Async避免阻塞 - 异常处理:
@ControllerAdvice全局处理 - 配置管理:
application.yml中配置水印位置、大小限制等
为什么要人工做:AI不了解你的项目是否已存在FileStorageService、使用的是什么OSS SDK、日志规范是什么。拆解得越清晰,AI生成的代码越能直接融入现有项目。
第二步:与AI对话式设计(人机协作)
做什么:把拆解好的模块,逐个与AI讨论技术方案。这不是简单的“给我代码”,而是像跟资深同事讨论设计。
示例对话:
我:我要用Spring Boot 3.2 + Java 17实现图片上传加水印。图片可能10MB+,要求不阻塞主线程。水印支持文字(可配置内容、位置、透明度)和图片水印。你有什么方案建议?
AI:建议方案:
- Controller立即返回
202 Accepted和任务ID- 用
@Async+ThreadPoolTaskExecutor异步处理- 水印处理用javax.imageio.ImageIO(需注意大图内存问题)
- 处理完成后更新数据库状态,前端轮询或WebSocket通知
- 大图建议先用
Image.getScaledInstance或第三方库(如TwelveMonkeys)处理需要注意:ImageIO默认支持的格式有限,需要添加依赖;异步任务中的异常要妥善记录。
我:ImageIO处理大图确实容易OOM,有没有更好的方案?
AI:可以考虑:
- 使用ImageMagick命令行(通过ProcessBuilder),稳定但依赖外部
- 使用thumbnailator库(封装了ImageIO但做了优化)
- 使用JDK 21+的虚拟线程处理大量并发
根据你的环境,建议用thumbnailator,代码简单且内存控制较好。
这一步的价值:AI帮你想到Java特有的问题(内存溢出、线程池配置、流未关闭等),你在对话中修正方案。
第三步:生成代码骨架与细节(AI主导)
做什么:方案确定后,让AI生成代码。我习惯按模块分层生成:先实体类,再Repository,再Service,最后Controller。
示例提示词:
请用Spring Boot 3.2 + Java 17写一个图片上传和水印服务:
- 实体类
ImageTask:包含id(UUID)、原始文件名、原图路径、水印图路径、状态(PENDING/PROCESSING/COMPLETED/FAILED)、创建时间、错误信息- Repository:继承JpaRepository
- Service:
uploadAndWatermark(MultipartFile file, String watermarkText):保存原图,创建任务记录,触发异步水印处理,返回任务IDprocessWatermarkAsync(taskId, watermarkText):异步方法,用thumbnailator添加文字水印(右下角,半透明,字体大小根据图片比例计算)- Controller:POST /api/images/upload,返回任务ID;GET /api/images/task/{id} 查询状态
- 配置文件支持:
app.watermark.text=MyApp 2026、app.watermark.position=bottom-right、app.max-file-size=5MB
AI的输出:
// ImageTask.java @Entity @Table(name = "image_task") @Data @NoArgsConstructor @AllArgsConstructor public class ImageTask { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private String originalFileName; private String originalPath; private String watermarkedPath; @Enumerated(EnumType.STRING) private TaskStatus status; private LocalDateTime createdAt; private String errorMessage; @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); status = TaskStatus.PENDING; } } // WatermarkService.java @Service @Slf4j public class WatermarkService { @Value("${app.watermark.text:MyApp 2026}") private String watermarkText; @Autowired private ImageTaskRepository taskRepository; @Async("watermarkExecutor") public CompletableFuture<Void> processWatermarkAsync(UUID taskId, String watermarkText) { // 更新状态为PROCESSING ImageTask task = taskRepository.findById(taskId).orElseThrow(); task.setStatus(TaskStatus.PROCESSING); taskRepository.save(task); try { // 使用thumbnailator添加水印 File originalFile = new File(task.getOriginalPath()); File outputFile = new File(task.getWatermarkedPath()); BufferedImage original = ImageIO.read(originalFile); int fontSize = Math.max(20, original.getWidth() / 20); // 添加文字水印 Graphics2D g = original.createGraphics(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); g.setFont(new Font("Arial", Font.BOLD, fontSize)); g.setColor(Color.WHITE); FontMetrics fm = g.getFontMetrics(); int textWidth = fm.stringWidth(watermarkText); int textHeight = fm.getHeight(); int x = original.getWidth() - textWidth - 20; int y = original.getHeight() - 20; g.drawString(watermarkText, x, y); g.dispose(); // 保存水印图 String format = originalFile.getName().substring(originalFile.getName().lastIndexOf('.') + 1); ImageIO.write(original, format, outputFile); // 更新状态为COMPLETED task.setStatus(TaskStatus.COMPLETED); taskRepository.save(task); } catch (Exception e) { log.error("水印处理失败, taskId: {}", taskId, e); task.setStatus(TaskStatus.FAILED); task.setErrorMessage(e.getMessage()); taskRepository.save(task); } return CompletableFuture.completedFuture(null); } }我的工作:复制代码到IDE,但不急着运行。我逐行检查:
- 依赖注入是否正确(
@Autowiredvs 构造器注入) - 事务边界(异步方法中
@Transactional无效,需要手动处理) - 资源释放(
ImageIO.read和original.createGraphics()没有显式关闭,虽然有GC但最好用try-with-resources?这里需要改进) - 配置项读取是否合理
第四步:人工审阅与调整(人工主导)
做什么:AI生成的代码通常有90%可用,但总有需要微调的地方。
实际操作(针对上面的代码):
- 发现问题:AI用
Graphics2D直接修改原图BufferedImage,然后覆盖保存?不对,它读的是原图,画完直接写出,没有保存原图版本。我需要修改逻辑:先复制一份原图到临时文件,再对复制品加水印。 - 性能问题:
ImageIO.read(originalFile)会把整个10MB图片加载到内存,高并发下会OOM。我改用thumbnailator的流式处理,或者限制并发数。 - 线程池配置:AI只用了
@Async,没配置线程池。我补充ThreadPoolTaskExecutor配置:
@Configuration @EnableAsync public class AsyncConfig { @Bean(name = "watermarkExecutor") public Executor watermarkExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setThreadNamePrefix("watermark-"); executor.initialize(); return executor; } }- 文件存储:AI直接用了绝对路径,我需要改成从配置读取存储根目录,并自动创建目录。
- 格式处理:AI写的保存格式提取逻辑有bug(比如"jpg"和"jpeg"),我手工修复。
关键原则:AI是副驾驶,方向盘在我手里。我不会让任何一行我不理解的代码进入代码库。
第五步:测试生成与调试(人机协作)
做什么:让AI帮我生成JUnit测试用例,然后运行测试,把错误信息直接抛给AI辅助修复。
示例:
我:请为上面的
WatermarkService.processWatermarkAsync方法写JUnit 5测试用例,使用Mockito mock ImageIO和文件操作。覆盖场景:正常添加水印、图片格式不支持、文件不存在。
AI生成测试:
@SpringBootTest @ExtendWith(MockitoExtension.class) class WatermarkServiceTest { @Mock private ImageTaskRepository taskRepository; @InjectMocks private WatermarkService watermarkService; @Test void testProcessWatermarkSuccess() throws Exception { // Mock任务 ImageTask task = new ImageTask(); task.setId(UUID.randomUUID()); task.setOriginalPath("/tmp/test.jpg"); task.setWatermarkedPath("/tmp/output.jpg"); when(taskRepository.findById(any())).thenReturn(Optional.of(task)); // 准备测试图片 BufferedImage testImage = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB); when(ImageIO.read(any(File.class))).thenReturn(testImage); // 执行 watermarkService.processWatermarkAsync(task.getId(), "Test").get(); // 验证最终状态为COMPLETED ArgumentCaptor<ImageTask> captor = ArgumentCaptor.forClass(ImageTask.class); verify(taskRepository, atLeastOnce()).save(captor.capture()); assertThat(captor.getValue().getStatus()).isEqualTo(TaskStatus.COMPLETED); } }我运行测试,发现一个bug:
测试失败:NullPointerException at WatermarkService.processWatermarkAsync - g.setFont() 原因:在headless环境下,Graphics2D的字体渲染可能失败。我:测试在GitHub Actions无头环境下失败了,提示
GraphicsEnvironment.getLocalGraphicsEnvironment()返回null。请修复代码,使其在无头环境下也能运行。
AI提供修复方案:使用BufferedImage的createGraphics()之前,先设置系统属性java.awt.headless=true(通常Spring Boot默认已设置),或者改用Font.createFont()加载字体。
我采纳后测试通过。
第六步:文档与注释(AI辅助)
做什么:代码写完后,让AI根据代码生成JavaDoc、OpenAPI注解(SpringDoc)和commit message。
示例:
请为上面的WatermarkService类生成标准的JavaDoc注释,包括参数说明、异常说明、使用示例。
AI输出:
/** * 图片水印处理服务 * * <p>异步处理图片水印添加,支持文字水印和图片水印。 * 处理结果会更新到ImageTask实体中,可通过状态查询接口获取结果。</p> * * <p>使用示例:</p> * <pre> * CompletableFuture<Void> future = watermarkService.processWatermarkAsync(taskId, "My Watermark"); * future.whenComplete((result, ex) -> { * if (ex != null) log.error("处理失败", ex); * }); * </pre> * * @author Your Name * @since 1.0.0 * @see ImageTask * @see TaskStatus */ @Service public class WatermarkService { ... }第七步:代码审查与重构(人机协作)
做什么:把写好的复杂代码块交给AI,让它提出重构建议,尤其是Java特有的设计模式、性能优化。
示例提示词:
请审查下面这段批量处理图片的代码,是否存在线程安全问题?是否可以用Stream并行流优化?是否违反了单一职责原则?
AI可能会指出:
@Async方法中直接修改实体并保存,如果同一个任务被并发调用会有竞态条件(用@Version乐观锁)- 可以把水印绘制逻辑抽取成独立的
WatermarkRenderer接口,便于单元测试和扩展图片水印 - 文件路径拼接用
Path而不是字符串,避免跨平台问题
三、完整流程图示
需求输入 ↓ [人工] 拆解任务 → 定义接口/实体/分层 ↓ [人机] 技术方案讨论(Spring版本、线程池、格式兼容) ↓ [AI] 生成POJO + Repository → 代码块 [AI] 生成Service(含异步)→ 代码块 [AI] 生成Controller + DTO → 代码块 ↓ [人工] 审阅调整(事务边界、资源释放、配置抽取) ↓ [AI] 生成JUnit测试 → 测试代码 ↓ [人工] 运行测试 → 发现bug(如headless、OOM) ↓ [人机] 调试修复(循环直到通过) ↓ [AI] 生成JavaDoc + OpenAPI注解 → 文档 ↓ [人工] 最终审查 + Git提交 ↓ [人机] 重构建议(设计模式、性能优化)四、详细总结
1. 核心变化
| 方面 | 以前 | 现在 |
|---|---|---|
| 样板代码 | 手写POJO的getter/setter/toString | Lombok + AI生成,瞬间完成 |
| Spring配置 | 翻文档写@Bean | AI生成配置类,微调即可 |
| 异常处理 | 忘记try-catch或处理不当 | AI生成全局@ControllerAdvice |
| 单元测试 | 懒得写,或覆盖率低 | AI生成骨架,只需补充断言 |
| 第三方集成 | 读SDK文档、写样板 | AI给出最佳实践代码 |
2. AI使用技巧
- 明确JDK版本:提示词中包含“Java 17”,避免AI生成Java 8过时API或Java 21专有特性
- 指定框架版本:“Spring Boot 3.2 + Jakarta EE(不是javax)”
- 提供现有类信息:把已有的
BaseEntity、ResultVO类代码贴给AI,让它生成的代码遵循项目规范 - 注意Lombok:提醒AI使用
@Data、@Builder等,减少冗余代码 - 内存敏感代码人工复核:AI经常忽略Java的OOM风险(大文件、集合无限增长、流未关闭)
3. 效率提升数据
| 任务类型 | 以前耗时 | 现在耗时 | 变化 |
|---|---|---|---|
| 写JPA实体类 + Repository | 10分钟 | 2分钟 | -80% |
| 写Service CRUD + 分页 | 15分钟 | 4分钟 | -73% |
| 写全局异常处理 | 20分钟 | 5分钟 | -75% |
| 写单元测试(Mockito) | 20分钟 | 5分钟 | -75% |
| Debug Spring循环依赖 | 15分钟 | 8分钟 | -47% |
4. 常见陷阱与应对
| 陷阱 | 表现 | 应对策略 |
|---|---|---|
| 版本混淆 | AI用了javax.persistence而不是jakarta.persistence | 明确指定“Jakarta EE 10” |
| 事务失效 | @Async方法内调@Transactional不生效 | 提醒AI“异步方法中事务如何正确使用” |
| 内存泄漏 | 不关闭Stream、Connection | 人工检查所有资源类是否用了try-with-resources |
| 线程安全 | SimpleDateFormat、ArrayList在并发下崩溃 | AI生成代码后,人工检查是否用了线程安全类 |
| Null安全 | 没有@NotNull、Optional滥用 | 让AI生成时强制“使用java.util.Optional避免null” |
5. 最后
以前开发效率常被样板代码拖累。AI的到来,让开发体验发生了质变:
- Lombok + AI:两者结合几乎消灭了POJO的所有手写工作
- Spring生态整合:AI熟悉Spring全家桶的配置方式,节省了大量查文档时间
- 类型安全仍需要你:Java的强类型对AI来说“容易生成但容易错”,比如泛型擦除后的类型转换、Stream的类型推断,需要你人工校验
实践:
- 用AI生成标准代码(Controller/Service/Repository)
- 人工编写核心算法和复杂业务逻辑
- 让AI帮忙写测试和文档
- 最终执行严格的Code Review(你Review AI的代码)
AI让开发从“打字密集型”变成了“决策密集型”。你的价值不再是敲出多少行代码,而是做出多少个正确的技术决策。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海
