POI+自定义注解+反射导出Excel(已测试)

实现效果

涉及的简单的报表导出时,如果每个导出都手动写是一件很麻烦的事情,尤其当导出的样式模板都是一样的时候,因此,这边使用自定义注解+反射来实现动态导出的效果,避免重复代码工作。主要就是了解poi对excel的描绘机制(从上到下:先描绘行,从左到右:再描绘列),以行+列定义出cell;另外就是注解和反射的知识稍微了解一下。

maven依赖

    <!-- servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>

    <!-- poi 选择3.8之后 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.11-beta2</version>
    </dependency>

poi的选择

依赖选择:详细介绍了各个依赖包的作用

3.8+后的POI:简单对比了一下,开发环境下1000/10000/100000条数据测试是会快一点,代码编写使用时没有太大差别

代码编写

1.定义注解

首先定义注解,为了后续可以用于动态控制导出字段以及做反射取值。

/**
 * @Auth: chang
 * @Date: 2020/12/15
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ExcelHeader {

    String name();

    int sort() default 10;

    String getMethod();
}

2.定义数据体

把导出的excel文件抽象当作一个model,正常来说会有 sheettitleheaderdata四个属性,主要就是sheet的值,标题,表头和数据。

import java.util.List;

/**
 * @Auth: chang
 * @Date: 2020/12/17
 */
public class ExcelModel<T> {

    /**
     * sheet 名称
     */
    private String sheetName;

    /**
     * 标题名称
     */
    private String titleName;

    /**
     * 数据体
     */
    private List<T> data;

    public ExcelModel(String sheetName, String titleName, List<T> data) {
        this.sheetName = sheetName;
        this.titleName = titleName;
        this.data = data;
    }

    public String getSheetName() {
        return sheetName;
    }

    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }

    public String getTitleName() {
        return titleName;
    }

    public void setTitleName(String titleName) {
        this.titleName = titleName;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }
}

3.帮助处理类

做完准备工作就差动态处理了,这里的嵌套循环+反射的代码可以再优化优化。其中定义了一些接口,用于存常量和做扩展。常量比较好理解,扩展则是jdk1.8版本后的接口增强有了default方法,spring里也出现了不少过时Adapter改用接口代替。

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.*;

import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Auth: chang
 * @Date: 2020/12/15
 */

public class ExcelTemplate<T> {

    /**
     * 标题 行起始 下标
     */
    private int T_R;

    /**
     * 标题 列起始 下标
     */
    private int T_C;

    /**
     * 标题 所占行数
     */
    private int T_H;

    /**
     * 数据 列起始 下标 默认==标题行起始+标题高度
     */
    private int D_R;

    /**
     * 数据 列起始 下标 默认==标题列起始
     */
    private int D_C;

    /**
     * 文件写入路径
     */
    private String filePath;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 网络请求响应
     */
    private HttpServletResponse response;

    /**
     * 数据体
     */
    private ExcelModel<T> model;

    /**
     * 数据样式
     */
    private ExcelStyle excelStyle;

    /**
     * 可以接收一个自定义style
     * @see ExcelStyle 实现类
     * @param excelStyle 自定义style
     */
    public void setExcelStyle(ExcelStyle excelStyle) {
        this.excelStyle = excelStyle;
    }

    public ExcelTemplate (ExcelModel<T> model, String fileName, String filePath){
        this(model, fileName, filePath, null);
    }

    public ExcelTemplate (ExcelModel<T> model, String fileName, HttpServletResponse response){
        this(model, fileName, null, response);
    }

    public ExcelTemplate (ExcelModel<T> model, String fileName, String filePath, HttpServletResponse response){
        T_R = 0;
        T_C = 0;
        T_H = 1;
        D_R = 1;
        D_C = 0;
        this.fileName = fileName;
        this.filePath = filePath;
        this.model = model;
        this.response = response;
    }

    public ExcelTemplate (int t_R, int t_C, int t_H, ExcelModel<T> model, String fileName, String filePath, HttpServletResponse response){
        T_R = t_R;
        T_C = t_C;
        T_H = t_H;
        D_R = T_R + T_H;
        D_C = T_C;
        this.fileName = fileName;
        this.filePath = filePath;
        this.model = model;
        this.response = response;
    }
    
	/**
     * 这里是使用的 XSSFWorkbook
     */
    /*public void download() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
        if (null == excelStyle){
            excelStyle = new ExcelStyle() {};
        }

        if (null == filePath && null == response){
            throw new RuntimeException("filePath和response不可同时为null");
        }
        if (null == model.getData() || model.getData().size() == 0){
            return;
        }
        //根据注解判断长度
        Class clazz = model.getData().get(0).getClass();
        Field[] fields = clazz.getDeclaredFields();
        List<ExcelHeader> excelHeaders = Arrays.stream(fields)
                .filter(field -> field.isAnnotationPresent(ExcelHeader.class))
                .map(field -> field.getAnnotation(ExcelHeader.class))
                .sorted(Comparator.comparing(ExcelHeader::sort))
                .collect(Collectors.toList());
        //创建工作薄对象
        XSSFWorkbook workbook = new XSSFWorkbook();
        //默认使用一个sheet
        XSSFSheet sheet = workbook.createSheet();
        //如果指定sheetName
        if (null != model.getSheetName()){
            workbook.setSheetName(0, model.getSheetName());
        }

        //绘制标题
        //1.起始行
        XSSFRow titleRow = sheet.createRow(T_R);
        //2.起始列
        XSSFCell titleCell = titleRow.createCell(T_C);
        //3.按表头长度合并
        sheet.addMergedRegion(new CellRangeAddress(T_R, T_R+T_H-1, T_C, T_C+excelHeaders.size()-1));
        //4.设置标题
        titleCell.setCellValue(model.getTitleName());
        //5.设置样式
        //titleCell.setCellStyle(excelStyle.getTitleStyle(workbook));

        //绘制表头
        //1.定义表头行 默认占一行
        XSSFRow tableHeader = sheet.createRow(D_R);
        //2.遍历描绘表头
        int t = 0;
        for (int i = T_C; i< T_C+excelHeaders.size(); i++){
            //表头格
            XSSFCell headerCell = tableHeader.createCell(i);
            //类型为字符串
            headerCell.setCellType(XSSFCell.CELL_TYPE_STRING);
            //取出定义表名
            XSSFRichTextString textString = new XSSFRichTextString(excelHeaders.get(t).name());
            //表头赋值
            headerCell.setCellValue(textString);
            //设置样式
            //headerCell.setCellStyle(excelStyle.getHeaderStyle(workbook));
            //为同步遍历headers
            t++;
        }

        //绘制表体 默认表头起始+1
        int height = model.getData().size() + D_R + 1;
        //遍历数据列表
        int i = 0;
        for (int row=D_R + 1; row<height; row++){
            //创建数据行
            XSSFRow dataRow = sheet.createRow(row);
            //遍历描绘数据
            int width = excelHeaders.size() + D_C;
            //遍历表头列表
            int j = 0;
            for (int col=D_C; col<width; col++){
                //描绘格子
                XSSFCell dataCell = dataRow.createCell(col);
                T entity = model.getData().get(i);
                String value = clazz.getMethod(excelHeaders.get(j).getMethod()).invoke(entity).toString();
                dataCell.setCellValue(value);
                //dataCell.setCellStyle(excelStyle.getCellStyle(workbook));
                j++;
            }
            i++;
        }

        //自适应列宽
        for (int start = T_C; start< T_C+ excelHeaders.size(); start++){
            sheet.autoSizeColumn(start);
        }

        if (null != filePath && !"".equals(filePath)){
            FileOutputStream outputStream = new FileOutputStream(filePath + fileName);
            workbook.write(outputStream);
            outputStream.close();
        }

        if (response != null) {
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=\"" + new String(fileName.getBytes("gb2312"), "ISO8859-1"));
            OutputStream out = response.getOutputStream();
            workbook.write(out);
            out.close();
        }

    }*/
	
	/**
     * 这里是使用的 SXSSFWorkbook
     */
    public void download() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {

        if (null == filePath && null == response){
            throw new RuntimeException("filePath和response不可同时为null");
        }

        if (null == model.getData() || model.getData().size() == 0){
            return;
        }

        if (!fileName.contains(".")){
            fileName = fileName + ExcelConatant.XLSX_SUFFIX;
        }

        if (!fileName.toLowerCase().endsWith(ExcelConatant.XLS_SUFFIX)
            && !fileName.toLowerCase().endsWith(ExcelConatant.XLSX_SUFFIX)){
            throw new RuntimeException("文件后缀只能是xls或xlsx");
        }

        if (null == excelStyle){
            excelStyle = new ExcelStyle() {};
        }
        //根据注解判断长度
        Class clazz = model.getData().get(0).getClass();
        Field[] fields = clazz.getDeclaredFields();
        List<ExcelHeader> excelHeaders = Arrays.stream(fields)
                .filter(field -> field.isAnnotationPresent(ExcelHeader.class))
                .map(field -> field.getAnnotation(ExcelHeader.class))
                .sorted(Comparator.comparing(ExcelHeader::sort))
                .collect(Collectors.toList());
        //创建工作薄对象
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        //默认使用一个sheet
        Sheet sheet = workbook.createSheet();
        //如果指定sheetName
        if (null != model.getSheetName()){
            workbook.setSheetName(0, model.getSheetName());
        }

        //绘制标题
        //1.起始行
        Row titleRow = sheet.createRow(T_R);
        //2.起始列
        Cell titleCell = titleRow.createCell(T_C);
        //3.按表头长度合并
        sheet.addMergedRegion(new CellRangeAddress(T_R, T_R+T_H-1, T_C, T_C+excelHeaders.size()-1));
        //4.设置标题
        titleCell.setCellValue(model.getTitleName());
        //5.设置样式
        titleCell.setCellStyle(excelStyle.getTitleStyle(workbook));

        //绘制表头
        //1.定义表头行 默认占一行
        Row tableHeader = sheet.createRow(D_R);
        //2.遍历描绘表头
        int t = 0;
        for (int i = T_C; i< T_C+excelHeaders.size(); i++){
            //表头格
            Cell headerCell = tableHeader.createCell(i);
            //类型为字符串
            headerCell.setCellType(XSSFCell.CELL_TYPE_STRING);
            //取出定义表名
            XSSFRichTextString textString = new XSSFRichTextString(excelHeaders.get(t).name());
            //表头赋值
            headerCell.setCellValue(textString);
            //设置样式
            headerCell.setCellStyle(excelStyle.getCellStyle(workbook, (short) 10));
            //为同步遍历headers
            t++;
        }

        //绘制表体 默认表头起始+1
        int height = model.getData().size() + D_R + 1;
        //遍历数据列表
        int i = 0;
        for (int row=D_R + 1; row<height; row++){
            //创建数据行
            Row dataRow = sheet.createRow(row);
            //遍历描绘数据
            int width = excelHeaders.size() + D_C;
            //遍历表头列表
            int j = 0;
            for (int col=D_C; col<width; col++){
                //描绘格子
                Cell dataCell = dataRow.createCell(col);
                T entity = model.getData().get(i);
                String value = clazz.getMethod(excelHeaders.get(j).getMethod()).invoke(entity).toString();
                dataCell.setCellValue(value);
                dataCell.setCellStyle(excelStyle.getCellStyle(workbook, (short) 9));
                j++;
            }
            i++;
        }

        //自适应列宽
        for (int start = T_C; start< T_C+ excelHeaders.size(); start++){
            sheet.autoSizeColumn(start);
        }

        //写入磁盘空间
        if (null != filePath && !"".equals(filePath)){
            FileOutputStream outputStream = new FileOutputStream(filePath + fileName);
            workbook.write(outputStream);
            outputStream.close();
        }

        //写入response
        if (response != null) {
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=\"" + new String(fileName.getBytes("gb2312"), "ISO8859-1"));
            OutputStream out = response.getOutputStream();
            workbook.write(out);
            out.close();
        }
    }

}
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;

/**
 * @Auth: chang
 * @Date: 2020/12/17
 */
public interface ExcelStyle {

    default CellStyle getTitleStyle(Workbook workbook){
        //设置字体
        Font font = workbook.createFont();
        font.setBoldweight((short) 400);
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 14);
        //设置样式
        CellStyle style = workbook.createCellStyle();
        style.setFont(font);
        style.setWrapText(false);
        style.setAlignment(XSSFCellStyle.ALIGN_CENTER);
        style.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
        style.setFillBackgroundColor((short) 1);
        return style;
    }

    default CellStyle getCellStyle(Workbook workbook, short fontSize){
        //设置字体
        Font font = workbook.createFont();
        font.setBoldweight((short) 400);
        font.setFontName("宋体");
        font.setFontHeightInPoints(fontSize);
        //设置样式
        CellStyle style = workbook.createCellStyle();
        //设置底边框;
        style.setBorderBottom(XSSFCellStyle.BORDER_THIN);
        //设置底边框颜色;
        style.setBottomBorderColor(IndexedColors.BLACK.index);
        //设置左边框;
        style.setBorderLeft(XSSFCellStyle.BORDER_THIN);
        //设置左边框颜色;
        style.setLeftBorderColor(IndexedColors.BLACK.index);
        //设置右边框;
        style.setBorderRight(XSSFCellStyle.BORDER_THIN);
        //设置右边框颜色;
        style.setRightBorderColor(IndexedColors.BLACK.index);
        //设置顶边框;
        style.setBorderTop(XSSFCellStyle.BORDER_THIN);
        //设置顶边框颜色;
        style.setTopBorderColor(IndexedColors.BLACK.index);
        style.setFont(font);
        style.setWrapText(false);
        style.setAlignment(XSSFCellStyle.ALIGN_CENTER);
        style.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
        return style;
    }

/**
 * @Auth: chang
 * @Date: 2020/12/17
 */
public interface ExcelConatant {

    String XLS_SUFFIX = ".xls";

    String XLSX_SUFFIX = ".xlsx";
}

4.简单说明

个人感觉代码还是比较好理解的,和开头说的差不多,主要是利用注解+反射来动态定义导出字段和获取值,再利用poi去做绘制。这里留下了一个Style的接口可以自定义实现,便于不同的需求;然后自定义起始绘制坐标等等,有需要多个sheet也可以copy再自己实现一下。

演示

import excel.ExcelHeader;

/**
 * @Auth: chang
 * @Date: 2020/12/16
 */
public class User {

    @ExcelHeader(name = "姓名", sort = 3, getMethod = "getName")
    private String name;

    @ExcelHeader(name = "年龄", sort = 5, getMethod = "getAge")
    private int age;

    @ExcelHeader(name = "住址", sort = 1, getMethod = "getAddr")
    private String addr;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}
import excel.ExcelModel;
import excel.ExcelTemplate;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Auth: chang
 * @Date: 2020/12/16
 */
public class Test {

    public static void main(String[] args) throws Exception{
        List<User> users = new ArrayList<>();
        for (int i=0; i<1000; i++){
            User user = new User();
            user.setName("用户"+i);
            user.setAge(18+i);
            user.setAddr("家庭住址"+i);
            users.add(user);
        }

        ExcelModel<User> model = new ExcelModel<>("文档", "12月用户信息档案", users);
        ExcelTemplate template = new ExcelTemplate<>(model, "测试文档3", "F:\\项目文档\\项目文档\\");
        System.out.println(new Date().toString());
        template.download();
        System.out.println(new Date().toString());
    }
}

导出效果图

导出效果图
这里可以自定义起始位置,代码有标注。
练练手,很多基础知识都忘了或者开始就没掌握牢,本来想模仿一下mybatis的反射,但是有点复杂了些,想着后面可能再优化优化,再加个解析的方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值