编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

基于POI的通用Excel的导出实现(poi实现excel导入导出)

wxchong 2024-08-18 00:33:01 开源技术 16 ℃ 0 评论

现状

在日常Java开发中,有时会遇到基于现有的查询结果来导出Excel文件的业务需求,一般会想到使用开源的POI库,大多数人应该会先从网上找几个例子,再复制粘贴到项目中,基于需求来set对应的单元格内容,代码中会存在许多定制化代码,如下图所示:


设计

  • 抽象表头信息ExcelHeader类

一般导出的Excel第一行都是表头信息,从第二行开始是需要导出的数据。我们可以使用一个Model类来定义Excel表头及所处列的信息。

// 实现Comparable接口是为了根据order字段排序,确定这个表头的标题放在列的位置顺序
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExcelHeader implements Comparable<ExcelHeader> {
    // 表头的标题名称
    private String title;
    // 表头的标题所在列的顺序
    private int order;
    // 单元格的宽度
    private int width;
    // 对应字段
    private String fieldName;

    @Override
    public int compareTo(ExcelHeader o) {
        return Integer.compare(order, o.order);
    }
}
  • 抽象数据模型注解

导出的数据在后端是基于某个Model类的List集合,我们可以定义一个注解来标识此Model的需要导出的字段,从而实现通用的导出。

// 用来在导出对象的field上加入的注解,通过该annotation说明某个属性所对应的标题
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface ExcelResources {

    // 导出字段的标题名称,用于构造ExcelHeader对象的title
    String title();

    // 导出字段在excel的顺序,用于构造ExcelHeader对象的order
    int order() default 9999;

    // 导出字段列的宽度,用于构造ExcelHeader对象的width
    int width() default 150;
}
  • 实现

1、先定义工具类通用的静态导出方法

 /**
     * @param objs      导出的数据集合,一般为后端DB查询结果
     * @param clz       导出的Excel内容Model类,其导出属性上需要加@ExcelResources
     * @param sheetName 导出的Excel的Sheet页名称
     * @param pageSize  导出的Excel的每个Sheet页的数据量,若objs.size()>pageSize,会写入到(objs.size()/pageSize+1)个Sheet页中
     * @param fileName  导出的Excel文件名,会加上_yyyyMMddHHmmss避免重名
     * @param response  HttpServletResponse
     */
public static void exportExcel(List<?> objs, Class<?> clz, String sheetName, int pageSize, String fileName,
                                   HttpServletResponse response)
            throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        if (objs == null || clz == null) {
            throw new NullPointerException("parameter must be not null");
        }
        OutputStream outputStream = null;
        try {
            Workbook wb = handleObj2Excel(objs, clz, sheetName, pageSize);
            setResponseHeader(response, fileName);
            // 定入到输出流
            outputStream = response.getOutputStream();
            wb.write(outputStream);
            // 刷新流
            outputStream.flush();
        } finally {
            IOUtils.closeQuietly(outputStream);
        }
    }

2、私有方法handleObj2Excel()会通过参数objs数据,clz上的自定义注解来反射获取数据并set到单元格中,并返回HSSFWorkbook对象。

private static HSSFWorkbook handleObj2Excel(List<?> objs, Class<?> clz, String sheetName, int pageSize)
            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        HSSFWorkbook wb = new HSSFWorkbook();
        // 获取excel标题列表并排序,详见下面私有方法
        List<ExcelHeader> headers = getHeaderList(clz);
        Collections.sort(headers);
        int size = objs.size();
        if (size > 0) {
            int sheetCount = size % pageSize == 0 ? size / pageSize : size / pageSize + 1;
            HSSFSheet sheet;
            // 设置第i个sheet页内的数据
            for (int i = 1; i <= sheetCount; i++) {
                 // 创建sheet页
                if (!StringUtils.isEmpty(sheetName)) {
                    sheet = wb.createSheet(sheetName + i);
                } else {
                    sheet = wb.createSheet();
                }
                HSSFRow row = sheet.createRow(0);
               // 设置head的数据格式,详见下面私有方法
                CellStyle titleStyle = setCellStyle(wb, POSITION_TITLE);
                for (int m = 0, len = headers.size(); m < len; m++) {
                    HSSFCell head = row.createCell(m);
                    head.setCellStyle(titleStyle);
                    head.setCellValue(headers.get(m).getTitle());
                    sheet.setColumnWidth(m, 40 * headers.get(m).getWidth());
                }
                // 设置body数据部分的格式
                int begin = (i - 1) * pageSize;
                int end = Math.min((begin + pageSize), objs.size());
                int rowCount = 1;
                CellStyle bodyStyle = setCellStyle(wb, POSITION_BODY);
                for (int n = begin; n < end; n++) {
                    row = sheet.createRow(rowCount);
                    Object obj = objs.get(n);
                    for (int x = 0, len = headers.size(); x < len; x++) {
                        Cell body = row.createCell(x);
                        body.setCellStyle(bodyStyle);
												// 反射获取值并set到对应的单元格中,
                        // objs中model的field与clz的field名称需要相同对应才能导出
                        body.setCellValue(BeanUtils.getProperty(obj, headers.get(x).getFieldName()));
                    }
                    rowCount++;
                }
            }
        }
        return wb;
    }

// 由参数clz来获取@ExcelResources注解并生成表头信息ExcelHeader类的集合
 private static List<ExcelHeader> getHeaderList(Class<?> clz) {
        Field[] ms = clz.getDeclaredFields();
        List<ExcelHeader> headers = new ArrayList<>(ms.length);
        for (Field m : ms) {
            String field = m.getName();
            if (m.isAnnotationPresent(ExcelResources.class)) {
                ExcelResources er = m.getAnnotation(ExcelResources.class);
                // 标题,序号,宽度,其成员变量
                headers.add(new ExcelHeader(er.title(), er.order(), er.width(), field));
            }
        }
        return headers;
    }

// 设置单元格样式
 private static CellStyle setCellStyle(Workbook workBook, String position) {
        CellStyle cellStyle = workBook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.GENERAL);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置单元格字体,表头字段可以大些
        Font headerFont = workBook.createFont();
        if (POSITION_TITLE.equals(position)) {
            headerFont.setFontHeightInPoints((short) 12);
            headerFont.setBold(true);
        } else {
            headerFont.setFontHeightInPoints((short) 10);
        }
        cellStyle.setFont(headerFont);
        return cellStyle;
    }

3、私有方法setResponseHeader()为设置响应头及文件名。

// 设置返回的响应头
private static void setResponseHeader(HttpServletResponse response, String fileName) {
        // 返回的文件名
        String resName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)
                + "_" + DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMddHHmmss") + ".xls";
        response.setContentType("application/vnd.ms-excel");
        // excel的导出设置(文件名必须去掉空格,否则出现问题)
        response.setHeader("Content-Disposition", "attachment;filename="
                + resName.replaceAll("\\s*", StringUtils.EMPTY));
    }

例子

1、先定义一个Model类,并将要导出的字段上使用@ExcelResources注解,如下图:

2、通过DB查询出数据后,直接调用工具类导出Excel。

3、查看导出的文件内容


扩展性

由于自定义注解,具有可扩展性,比如定义单元格的格式(字体,颜色,边框,类型 等)、添加单元格的校验(单选,多选,范围等)。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表