Java Spring应用导出方法代码重复优化:应选择独立方法、建造者模式还是模板方法?
如何消除Spring中Excel导出方法的代码重复
这是个非常典型的“固定流程+可变逻辑”场景,咱们可以用几种优雅且符合Java/Spring规范的方式解决,我来给你拆解最优方案:
先明确核心重复点
你的两个方法里,重复代码块I是Excel工作簿的初始化逻辑,重复代码块II是文件写入、转换为MultipartFile的收尾逻辑,唯一的差异是中间的数据获取与Excel填充逻辑。针对这个场景,最适合的是下面两种方案:
方案一:模板方法模式(推荐用于多导出场景)
模板方法模式完美适配这种“固定步骤包裹可变逻辑”的场景,通过抽象类封装固定流程,子类只需要实现差异化的部分。
1. 定义抽象基类封装固定逻辑
public abstract class AbstractExcelExporter { private static final String EXTENSION = ".xlsx"; private static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; protected Workbook workbook; protected Sheet sheet; protected AtomicInteger rowCount; public MultipartFile export(String title) throws IOException { // 重复代码块I:固定初始化逻辑 workbook = new XSSFWorkbook(); sheet = workbook.createSheet(title); rowCount = new AtomicInteger(0); // 调用子类实现的差异化数据处理逻辑 processData(); // 重复代码块II:固定收尾逻辑 File outputFile = File.createTempFile(title, EXTENSION); try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { workbook.write(outputStream); } catch (IOException e) { LoggingUtils.error("Writing is failed ", e); } try (FileInputStream input = new FileInputStream(outputFile)) { String fileName = title.concat(EXTENSION); return new MockMultipartFile(fileName, fileName, CONTENT_TYPE, IOUtils.toByteArray(input)); } } // 抽象方法:子类必须实现自己的数据处理逻辑 protected abstract void processData() throws IOException; }
2. 实现具体导出类
针对Aaaaa和Bbbbb分别实现子类,只需要关注自己的差异化逻辑:
// Aaaaa导出类 public class AaaaaExporter extends AbstractExcelExporter { private final AaaaaService aaaaaService; private static final String TITLE_KEY = "aaaaa_export_title"; // Spring构造注入服务 public AaaaaExporter(AaaaaService aaaaaService) { this.aaaaaService = aaaaaService; } @Override protected void processData() { Page<Aaaaa> page = aaaaaService.findAll(); // ... 这里写原来的Aaaaa专属Excel填充逻辑 } public MultipartFile exportAaaaa() throws IOException { return export(TextBundleUtil.read(TITLE_KEY)); } }
// Bbbbb导出类 public class BbbbbExporter extends AbstractExcelExporter { private final BbbbbService bbbbbService; private final String uuid; private static final String TITLE_KEY = "bbbbb_export_title"; public BbbbbExporter(BbbbbService bbbbbService, String uuid) { this.bbbbbService = bbbbbService; this.uuid = uuid; } @Override protected void processData() { Page<Bbbbb> page = bbbbbService.findByUuid(uuid); // ... 这里写原来的Bbbbb专属Excel填充逻辑 } public MultipartFile exportBbbbb() throws IOException { return export(TextBundleUtil.read(TITLE_KEY)); } }
方案优势
- 完全消除代码重复,固定逻辑集中维护,修改时只改一处
- 符合开闭原则:新增导出类型时,只需要新增子类实现
processData即可 - 结构清晰,适合后续扩展更多导出需求
方案二:函数式接口(轻量方案,适合少导出场景)
如果不想用继承,也可以利用Java 8+的函数式接口,把差异化逻辑作为参数传递给通用方法,更轻量灵活。
1. 定义函数式接口
@FunctionalInterface public interface ExcelDataProcessor { void process(Sheet sheet, AtomicInteger rowCount) throws IOException; }
2. 在原类中实现通用导出方法
@Service public class ExcelExportService { private static final String EXTENSION = ".xlsx"; private static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private final AaaaaService aaaaaService; private final BbbbbService bbbbbService; // Spring构造注入服务 public ExcelExportService(AaaaaService aaaaaService, BbbbbService bbbbbService) { this.aaaaaService = aaaaaService; this.bbbbbService = bbbbbService; } // 通用导出方法,接收标题和数据处理器 private MultipartFile export(String title, ExcelDataProcessor processor) throws IOException { // 重复代码块I Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet(title); AtomicInteger rowCount = new AtomicInteger(0); // 执行传入的差异化逻辑 processor.process(sheet, rowCount); // 重复代码块II File outputFile = File.createTempFile(title, EXTENSION); try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { workbook.write(outputStream); } catch (IOException e) { LoggingUtils.error("Writing is failed ", e); } try (FileInputStream input = new FileInputStream(outputFile)) { String fileName = title.concat(EXTENSION); return new MockMultipartFile(fileName, fileName, CONTENT_TYPE, IOUtils.toByteArray(input)); } } @Override public MultipartFile exportAaaaa() throws IOException { String title = TextBundleUtil.read("aaaaa_export_title"); return export(title, (sheet, rowCount) -> { Page<Aaaaa> page = aaaaaService.findAll(); // ... Aaaaa专属填充逻辑 }); } @Override public MultipartFile exportBbbbb() throws IOException { String title = TextBundleUtil.read("bbbbb_export_title"); return export(title, (sheet, rowCount) -> { Page<Bbbbb> page = bbbbbService.findByUuid(uuid); // ... Bbbbb专属填充逻辑 }); } }
方案优势
- 无需额外创建类,代码更紧凑
- 利用Lambda表达式简化逻辑传递,适合只有少量导出方法的场景
- 避免继承带来的类层级复杂度
关于你提到的其他方案分析
- 建造者模式:更适合构建复杂可配置的对象(比如自定义Excel样式、多Sheet等),你的场景是固定流程+可变数据,用建造者会增加不必要的复杂度,不是最优选择。
- 独立方法抽离重复代码:虽然可行,但如果后续新增导出方法,还是会重复调用这些独立方法,不如上述两种方案的可扩展性强。
- 继承:其实方案一的模板方法模式就是基于继承的合理实现,核心是把公共逻辑放在父类,子类只负责差异化部分,是符合设计规范的。
内容的提问来源于stack exchange,提问作者user17188729
相关产品推荐
相关产品推荐

