You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 21:03:11