强大!Spring Boot 一个注解导出任意Excel

环境: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 ;
  @Override  public boolean supportsReturnType(MethodParameter returnType) {    // 只对@ExportExcel注解的方法生效    return returnType.hasMethodAnnotation(ExportExcel.class);  }  @Override  public 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);  }  @Override  public 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;  }  @Override  public 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 导出操作。不过,若导出的数据量极为庞大,开发者需要根据实际业务场景和性能要求,自行对自定义注解及对应的处理器进行扩展优化。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值