☰
Spring 项目中如何组织 JQuick-Excel 的 XML 与 Bean
2026/10/18 6:05:55 网站建设 项目流程

Spring 项目中如何组织 JQuick-Excel 的 XML 与 Bean

tags: #JQuickExcel #JavaExcel #SpringBoot #开源 #POI #Excel工具

更多示例: JQuick-Excel 官网示例 https://www.jquick.org/jquick-excel/#demo-formulas0005

简介

我是 JQuick-Excel 的作者。在 Spring 项目里接入 Excel,最容易失控的地方往往不是控制器,而是散落在业务代码里的字段映射、资源路径和流对象。本篇给出我实际建议的组织方式:把 JQuick-Excel DSL 放进 resources,把接口与 Spring Bean 分层管理,并始终通过JQuickXmlFactory、导入/导出解析器执行规则。

前言

我不会把 JQuick-Excel 当成一个需要“注解扫描后自动生效”的 Spring 组件。根据 README-CN 和仓库测试,核心调用链是明确的:Java 准备JQuickRow、JContext与输入或输出流;JQuickExcelExportXmlParseFactory或JQuickExcelImportXmlParseFactory保存本次执行所需资源;JQuickXmlFactory从 classpath XML 创建接口代理;代理再按方法名执行EXPORT WITH或IMPORT WITHDSL。

因此,我在 Spring 工程中把 Spring 的职责限定为依赖注入和 HTTP/任务调度,把 Excel 规则留在 XML。这里没有使用、也没有假定存在任何 JQuick-Excel 的 Spring Boot starter、自动配置、@Enable...注解或自动注册 Bean。即使项目本身采用 Spring Boot,JQuick-Excel 的核心仍由下面展示的工厂、解析器和 resources XML DSL 显式调用。

一个实用的目录划分如下:服务接口放在com.example.report.excel;DSL 放在src/main/resources/excel/student-excel.xml;负责组装运行时数据的 Spring Bean 放在service包。接口和 XML 是稳定契约,业务 DTO、下载流、上传流则是每次请求不同的输入。

环境与依赖

本文以 Java 8+、Spring Framework 或 Spring Boot Web 环境为前提。JQuick-Excel 使用 Maven 坐标io.github.paohaijiao:jquick-excel:3.6.0;Apache POI 是它的项目依赖,不需要为了本文另写一套 POI 读写代码。

<dependency><groupId>io.github.paohaijiao</groupId><artifactId>jquick-excel</artifactId><version>3.6.0</version></dependency>

将 XML 放入src/main/resources/excel/student-excel.xml。JQuickXmlFactory接收的是 classpath 资源名,所以后续 Java 必须传入excel/student-excel.xml,而不是开发机绝对路径。namespace必须等于接口全限定名,excel/@name必须等于接口方法名。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEexcelsPUBLIC"-//PAOHAIJIAO//DTD API EXCEL 1.0//EN""classpath:paohaijiao/dtd/Jquick-excel.dtd"><excelsnamespace="com.example.report.excel.StudentExcelApi"><excelname="exportStudents"returnClass="void"><![CDATA[ EXPORT WITH SHEET="学生表", HEADER=true, MAPPING={"id":"学号","name":"姓名","gender":"性别","enrollmentDate":"入学日期"}, TRANSFORM={ "gender":trans(${dict},${gender}), "enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd') } ]]></excel><excelname="importStudents"returnClass="java.util.List"><![CDATA[ IMPORT WITH SHEET="学生表", HEADER=true, MAPPING={"学号":"id","姓名":"name","性别":"gender","入学日期":"enrollmentDate"}, TRANSFORM={"gender":trans(${dict},${gender})} ]]></excel></excels>

代码示例

我先定义一个只表达 XML 代理契约的接口。README-CN 与测试均使用@Param和List<JQuickRow>;下面没有杜撰 DTO 自动转换 API。

packagecom.example.report.excel;importcom.github.paohaijiao.statement.JQuickRow;importcom.github.paohaijiao.xml.param.Param;importjava.util.List;publicinterfaceStudentExcelApi{voidexportStudents(@Param("field")Stringfield,@Param("value")Stringvalue);List<JQuickRow>importStudents(@Param("field")Stringfield,@Param("value")Stringvalue);}

我把代理创建封装在 Spring 服务中,但没有把解析器或代理声明为单例 Bean。它们携带当前请求的行数据和流,复用会把不同请求的状态混在一起。导出每次新建解析器、工厂和 API,然后立即调用。

packagecom.example.report.service;importcom.example.report.excel.StudentExcelApi;importcom.github.paohaijiao.param.JContext;importcom.github.paohaijiao.statement.JQuickRow;importcom.github.paohaijiao.util.JObjectConverter;importcom.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;importcom.github.paohaijiao.xml.factory.JQuickFactory;importcom.github.paohaijiao.xml.factory.JQuickXmlFactory;importcom.github.paohaijiao.xml.handler.JQuickParseHandler;importorg.springframework.stereotype.Service;importjava.io.OutputStream;importjava.util.HashMap;importjava.util.List;importjava.util.Map;@ServicepublicclassStudentExcelService{publicvoidexport(List<Map<String,Object>>students,OutputStreamoutput){List<JQuickRow>rows=JQuickRow.toRows(JObjectConverter.convert(students));JContextcontext=newJContext();Map<String,Object>dict=newHashMap<>();dict.put("0","男");dict.put("1","女");context.put("dict",dict);JQuickParseHandlerparser=newJQuickExcelExportXmlParseFactory(context,rows,output);JQuickFactoryfactory=newJQuickXmlFactory(parser,"excel/student-excel.xml");StudentExcelApiapi=factory.createApi(StudentExcelApi.class);api.exportStudents("field","value");}}

导入同样由同一个服务接收InputStream,创建JQuickExcelImportXmlParseFactory(context, input),再调用api.importStudents。我通常让控制器或应用服务完成JQuickRow到领域对象的校验和保存,避免把持久化逻辑塞进 XML。

原理说明

XML 中的MAPPING在导出时是“内部字段到表头”,导入时是“表头到内部字段”。HEADER=true表示生成或识别第一行表头。TRANSFORM中的${dict}来自JContext,导出字典方向为编码到显示值;导入应反过来,例如“男”到“0”。同名dict可以存在,但不能不经转换就复用同一张映射表。

Spring Bean 的价值是把数据查询、字典准备和事务边界放在熟悉的应用层;它不替代 XML DSL。每次执行仍由JQuickXmlFactory读取资源,导入/导出解析器分别绑定数据流和文件流。这样报表字段、表头和转换变更集中在一个 resources 文件,HTTP、定时任务和消息消费等入口则可以共用 Spring 服务。

注意事项

  1. 不要把OutputStream、InputStream、导出解析器或接口代理缓存为单例字段;它们与单次文件处理绑定。
  2. excel/student-excel.xml必须被构建工具打进 classpath,部署包中遗漏资源会导致工厂无法定位 XML。
  3. 接口全限定名、namespace、方法名、returnClass必须同时对齐;改包名时尤其容易漏改 XML。
  4. DSL 只使用 README-CN 和测试 XML 已出现的EXPORT WITH、IMPORT WITH、SHEET、HEADER、MAPPING、TRANSFORM与dateFormat、trans。
  5. 本文未声称存在 JQuick-Excel Starter 或自动配置;Spring 注解仅来自标准 Spring 框架。

运行步骤与工程化组织

我在一个新的 Spring 工程中会先建立稳定的资源契约,再写 Controller。第一步,将接口放入普通 Java 包,将 XML 放入src/main/resources/excel,并让namespace和接口全限定名一致。第二步,写一个只接收List<Map<String, Object>>与OutputStream的导出服务,以及一个只接收InputStream的导入服务;二者内部每次调用都创建新的JContext、解析器、JQuickXmlFactory和 API 代理。第三步,用 JUnit 或现有集成测试生成一个最小 xlsx,再以该文件反向导入,验证工作表名、表头、字段映射和字典方向。只有该闭环通过后,再把服务接到 HTTP、定时任务或消息消费入口。

Bean 的边界比注解数量重要。数据查询、权限过滤、字典查询和事务由 Spring 服务负责;XML 资源名、接口名、DSL 规则由 Excel 适配层负责;Web Controller 只做请求解析、响应协议和调用编排。不要将InputStream、OutputStream、JQuickParseHandler、JQuickFactory或代理对象注入为单例字段,它们绑定的是一次文件处理。可以注入稳定的查询服务和配置对象,但应在方法体内根据当前请求的数据和流组装 JQuick-Excel 对象。

多份报表的组织方式也应保持可扫描。我的惯例是一个 XML 对应一个接口,资源名与报表语义一致,例如excel/student-excel.xml和StudentExcelApi;版本升级时只复制并改名规则文件,不覆盖仍被旧接口使用的模板。公共字典键可以统一约定,例如dict,但每个操作仍要明确编码到显示值还是显示值到编码。对于租户、语言或日期格式等运行期差异,应在 Spring 服务层准备JContext,不要通过修改 classpath XML 来承载每个请求的动态状态。

构建与部署时,我会检查最终 jar 是否包含excel/*.xml以及 DTD 所需的 classpath 资源。IDE 直接运行成功并不能证明打包成功,资源过滤、重命名或模块间依赖范围都可能让部署包缺少 XML。发布前至少执行一次打包后的启动测试,并调用导出、导入各一次。出现“代理找不到规则”时,先看资源名、接口包名和 XML 的excel/@name,不要立即把问题归因给 Spring Bean 扫描,因为本方案本来就不依赖 JQuick-Excel 自动扫描。

在事务上,我把文件解析和持久化分开考虑。导入得到List<JQuickRow>后,应用层应先完成字段校验、重复数据判断和错误聚合,再决定是否进入写库事务;不要在读取到每一行时立即提交不可逆业务操作。导出则先完成权限与数据范围校验,再取得响应流并开始写入;HTTP 响应开始提交后无法可靠替换成结构化错误 JSON,因此耗时查询和可预见的校验应尽量前置。以上都属于标准 Spring 应用组织,并不要求任何 JQuick-Excel Starter 或自动配置。

总结

我在 Spring 中采用“XML 管规则、Bean 管运行时资源”的方式:resources XML 定义导入导出 DSL,接口定义代理契约,Spring 服务在每次调用时创建JQuickXmlFactory与对应解析器。这样既保留 Spring 的组织能力,也不掩盖 JQuick-Excel 真正的执行入口,后续接入下载、上传或批量任务时都能复用同一套规则。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询