环境:SpringBoot3.4.2
1. 简介
在项目开发中,导出 Excel 是一项极为常见且重要的需求。然而,在传统开发模式下,实现 Excel 导出功能往往需要开发者编写大量重复代码。这不仅导致开发效率低下,而且由于代码的冗余和耦合度高,后期维护成本也高。一旦业务需求发生变化,就需要对大量代码进行修改,增加了出错的风险。
EasyExcel 是阿里巴巴开源的一个基于 Java 的简单、省内存的读写 Excel 工具。它采用基于 SAX 的解析模式,相较于传统的 POI 等工具,在处理大数据量时具有显著优势,能有效减少内存占用,提升读写性能。
注意,目前EasyExcel处于维护模式。
本篇文章我们将通过自定义注解 + EasyExcel 的方式,实现高效、便捷的 Excel 导出功能,以解决传统开发中的痛点。
如下最终代码效果:
@GetMapping("/simple")
@ExportExcel(fileName = "简单数据导出")
public ist<Object> simpleExport() {
}
@GetMapping("/user")
@ExportExcel(fileName = "用户数据导出", sheetName = "用户列表", modelClass = User.class)
public List<User> exportWithModel() {
}
// 支持SpEL表达式
@GetMapping("/dynamic")
// @ExportExcel(fileName = "#{'动态数据_' + T(java.time.LocalDate).now().toString()}", sheetName = "动态数据")
@ExportExcel(fileName = "#{#request.getParameter('filename')}", sheetName = "动态数据")
public List<List<Object>> dynamicExport() {
}
// 支持模板@GetMapping("/template")
@ExportExcel(fileName = "用户数据导出", modelClass = User.class, template = "classpath:templates/user.xlsx")
public List<User> exportWithTemplate() {
}
2.实战案例
2.1 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportExcel {
// 默认文件名;支持SpEL表达式
String fileName() default "export";
// 默认Sheet名;支持SpEL表达式
String sheetName() default "Sheet";
// 数据模型类
Class<?> modelClass() default void.class;
// 模板;支持SpEL表达式
String template() default "";
}
该注解最终是应用到Controller接口上的,而对应的数据就是接口的返回值,并且必须是List集合类型。
2.2 自定义返回值处理器
Controller接口上使用了 @ExportExcel 注解要能够自动的生成Excel并下载,那么我们就需要自定义 HandlerMethodReturnValueHandler 返回值处理器。
@Componentpublic class ExportExcelReturnValueHandler implements HandlerMethodReturnValueHandler, ApplicationContextAware {private final Pattern pattern = Pattern.compile("^#\\{(.*?)}$") ;private final ExpressionParser parser = new SpelExpressionParser() ;private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer() ;private ApplicationContext context ;@Overridepublic boolean supportsReturnType(MethodParameter returnType) {// 只对@ExportExcel注解的方法生效return returnType.hasMethodAnnotation(ExportExcel.class);}@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,NativeWebRequest webRequest) throws Exception {ExportExcel annotation = returnType.getMethodAnnotation(ExportExcel.class);if (annotation == null) {return;}HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class) ;response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 解析动态文件名(支持SpEL表达式)String fileName = evaluateExpression(returnType, annotation.fileName(), request);fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+","%20") + ".xlsx" ;response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);if (returnValue instanceof List) {List<?> data = (List<?>) returnValue ;Class<?> modelClass = annotation.modelClass();String template = annotation.template() ;Resource resource = null ;if (StringUtils.hasLength(template)) {resource = this.context.getResource(template) ;}String sheetName = evaluateExpression(returnType, annotation.sheetName(), request) ;if (modelClass != void.class) {ExcelWriterBuilder builder = EasyExcel.write(response.getOutputStream(), modelClass) ;if (resource.exists()) {builder.withTemplate(resource.getInputStream()).sheet().doFill(data) ;} else {builder.autoCloseStream(true).sheet(sheetName).doWrite(data) ;}} else {// 这里自行进行模板的处理ExcelWriterBuilder builder = EasyExcel.write(response.getOutputStream());if (resource.exists()) {builder.withTemplate(resource.getInputStream()) ;}builder.autoCloseStream(true).sheet(sheetName).doWrite(data);}mavContainer.setRequestHandled(true);}// ...}private String evaluateExpression(MethodParameter parameter, String expression, HttpServletRequest request) {Matcher matcher = pattern.matcher(expression);if (!matcher.matches()) {return expression ;}expression = matcher.group(1) ;StandardEvaluationContext context = new MethodBasedEvaluationContext(null,parameter.getMethod(), new Object[0], parameterNameDiscoverer) ;// 可以访问Request中的方法context.setVariable("request", request) ;// 解析表达式return parser.parseExpression(expression).getValue(context, String.class);}@Overridepublic void setApplicationContext(ApplicationContext context) throws BeansException {this.context = context ;}}
接下来,注册该处理器:
@Componentpublic class WebConfig implements WebMvcConfigurer {private final ExportExcelReturnValueHandler exportExcelHandler ;public WebConfig(ExportExcelReturnValueHandler exportExcelHandler) {this.exportExcelHandler = exportExcelHandler;}@Overridepublic void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {handlers.add(this.exportExcelHandler) ;}}
2.3 测试
实体对象
public class User {@ExcelProperty("编号")private Long id;@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age ;@ExcelProperty(value = "性别", converter = SexConverter.class)private Integer sexCode ;}
完整Controller接口
@Controller@RequestMapping("/exports")public class ExportController {// 简单导出@GetMapping("/simple")@ExportExcel(fileName = "简单数据导出")public List<Object> simpleExport() {return List.of(List.of("编号", "姓名", "年龄", "性别", "邮箱", "创建时间"),List.of(1, "张三", 22, 0),List.of(2, "李四", 33, 1));}// 使用模型类导出@GetMapping("/user")@ExportExcel(fileName = "用户数据导出", sheetName = "用户列表", modelClass = User.class)public List<User> exportWithModel() {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");List<User> list = new ArrayList<>() ;for (int i = 1; i <= 20; i++) {list.add(new User(i + 0L, "姓名 - " + i, new Random().nextInt(100), new Random().nextInt(2)));}return list ;}// 动态文件名导出@GetMapping("/dynamic")// @ExportExcel(fileName = "#{'动态数据_' + T(java.time.LocalDate).now().toString()}", sheetName = "动态数据")@ExportExcel(fileName = "#{#request.getParameter('filename')}", sheetName = "动态数据")public List<List<Object>> dynamicExport() {return List.of(List.of("产品", "价格", "库存"),List.of("手机", 3999, 100));}// 使用模板导出@GetMapping("/template")@ExportExcel(fileName = "用户数据导出", sheetName = "用户列表", modelClass = User.class, template = "classpath:templates/user.xlsx")public List<User> exportWithTemplate() {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");List<User> list = new ArrayList<>();for (int i = 1; i <= 20; i++) {list.add(new User(i + 0L, "姓名 - " + i, new Random().nextInt(100), new Random().nextInt(2)));}return list;}}
在实际开发中,Controller 接口仅需调用具体的 Service 层方法来完成 Excel 导出操作。不过,若导出的数据量极为庞大,开发者需要根据实际业务场景和性能要求,自行对自定义注解及对应的处理器进行扩展优化。
4395

被折叠的 条评论
为什么被折叠?



