利用POI手搓一个简单的excel导出工具(含完整代码)

利用POI手搓一个简单的excel导出工具(含完整代码)

  • POI简介
    全称Apache POI,使用Java编写的免费开源的跨平台的Java API。 是创建和维护操作各种符合 Office Open XML(OOXML)标准和微软的 OLE 2 复合文档格式(OLE2)的 Java API。用它可以使用 Java 读取和创建, 修改 MS Excel 文件. 而且, 还可以使用 Java 读取和创建 MS Word 和 MSPowerPoint 文件。Apache POI 提供 Java 操作 Excel 解决方案(适用于 Excel97-2008)

下面我们来利用poi提供的方法,来写一个简单的Excel导出工具,由于部分方法中的方法调用较为频繁,这里就不一一写出来了,源码在文章末尾。

首先,我们创建一个注解,用于标注导出字段,代码如下:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excel {

    /**
     * 列名
     */
    String name();

    /**
     * 日期格式
     */
    String dateFormat() default "";

    /**
     * 日期时间格式,用的jdk8的时间类,感觉两个格式字段有些多余了,但是Date类型操作起来没有LocalDate方便,先试试,不行就换
     * @return
     */
    String dateTimeFormat() default "";

    /**
     * 列高
     */
    int height() default 20;

    /**
     * 列宽
     */
    int width() default 20;

    /**
     * 当值为空时的默认值
     */
    String defaultValue() default "";

    /**
     * 类型:IS_EXPORT导入,IS_IMPORT导出,ALL导入导出
     */
    ExcelType excelType() default ExcelType.ALL;

    /**
     * 是否可选字段,如果为true时,会根据请求参数来判断该字段是否显示
     */
    boolean isOptional() default false;

    /**
     * 当isOptional属性为true时,会根据这个字段值来判断该列是否导出
     */
    String judgeStr() default "";

    /**
     * 值转换,将某些特定值转换为另一个值,如:0:女,1:男,2:未知
     */
    String valueConvert() default "";

    /**
     * 前缀
     */
    String prefix() default "";

    /**
     * 后缀
     */
    String suffix() default "";

    /**
     * 是否只读
     */
    boolean isReadOnly() default false;

    /**
     * 是否需要判空
     */
    boolean isBlank() default false;
}

注解类上的两个注解意义分别是:

  • @Target(ElementType.FIELD) 该注解只作用于字段
  • @Retention(RetentionPolicy.RUNTIME) 注解生命周期,运行期存在

上面便是注解字段,只完善了部分必要功能。

接下来编写excel的工具类,分别为导入和导出两个功能

首先先创建一个工具类,再在工具类中增加部分需要的字段

public class ExcelUtil <T>{
	/**
     * sheet的最大行数
     */
    private final static int SHEET_MAX_NUM=65536;

    /**
     * 数据导入/导出时开始的行数
     */
    private final static int DATA_BEGIN_INDEX=2;

    /**
     * 默认的工作表下标
     */
    private final static int DEFAULT_SHEET_INDEX=0;

    /**
     * 字段描述行下标
     */
    private final static int FIELD_ROW_INDEX =1;

    /**
     * 实体类型
     */
    private Class<T> clazz;

    /**
     * 工作蒲对象
     */
    private Workbook wb;

    /**
     * 工作表对象
     */
    private Sheet sheet;

    /**
     * 样式
     */
    private Map<String, CellStyle> style;

    /**
     * 标题合并单元格的数量
     */
    private int titleMergeCellNum;

    /**
     * excel中不显示的字段
     */
    private Map<String,String> judgeStrMap=new HashMap<>();

    /**
     * 存储字段和注解的集合
     */
    private List<Object[]> fieldAndExcelList;

    /**
     * 最大列高
     */
    private int height;
}

再重写它的构造函数,确定导出类型

public ExcelUtil(Class<T> clazz){
        this.clazz=clazz;
    }

开始编写导出方法,首先进行初始化类中的工作簿等对象

	/**
     * 初始化
     * @param request
     * @param sheetName
     */
    public void init(HttpServletRequest request,String sheetName) {
    	//创建工作蒲
		wb = new SXSSFWorkbook(300);
		//创建工作表
		sheet=wb.createSheet(sheetName);
		//初始化样式,具体样式和方法参考文档:http://poi.apache.org/apidocs/dev/org/apache/poi/hssf/usermodel/HSSFCellStyle.html
		style=new HashMap<>(3);
        CellStyle title=wb.createCellStyle();
        //设置填充色
        //设置边框样式
        setBorderStyle(title);
        //设置字体样式
        Font titleFont=wb.createFont();
        titleFont.setFontHeight((short)(16*20));
        titleFont.setFontName("Arial");
        title.setFont(titleFont);
        //设置对齐方式
        setBackgroundColorAndAlignment(title);
        //标题
        style.put("title",title);
        CellStyle field=wb.createCellStyle();
        setBorderStyle(field);
        //设置字体样式
        Font fieldFont=wb.createFont();
        fieldFont.setFontHeight((short)(12*20));
        fieldFont.setFontName("Arial");
        fieldFont.setBold(false);
        field.setFont(fieldFont);
        //设置对齐方式
        setBackgroundColorAndAlignment(field);
        //字段标题
        style.put("field",field);
        CellStyle data=wb.createCellStyle();
        setBorderStyle(data);
        DataFormat dataFormat =wb.createDataFormat();
        //默认为文本类型
        data.setDataFormat(dataFormat.getFormat("@"));
        //数据
        style.put("data",data);
        //获取类的字段信息
		Field [] fields=clazz.getDeclaredFields();
        //合并单元格时,由于是从0开始合并的,需要判断出第一个不用进行加减操作
        boolean isTitleMergeCellNum=true;
        fieldAndExcelList =new ArrayList<>();
        for (Field field : fields) {
        	//获取字段上的Excel注解,如果没有,或者不是导出类型,就返回进行下一次循环
            Excel excel = field.getAnnotation(Excel.class);
            if (excel == null||excel.excelType()== ExcelType.IS_IMPORT) {
                continue;
            }
            //获取单元格最大高度
            height=excel.height()>height?excel.height():height;
            //判断是否为可选字段,如果不是,添加完字段信息便返回
            if (!excel.isOptional()) {
                fieldAndExcelList.add(new Object[]{field, excel});
                if (isTitleMergeCellNum) {
                    isTitleMergeCellNum = false;
                    continue;
                }
                //标题所占单元格数
                titleMergeCellNum += 1;
                continue;
            }
            //如果是可选字段,判断前端传的参数如果参数和judgeStr相同,就不导出该字段
            String judgesStrValue = request.getParameter(excel.judgeStr());
            if (judgesStrValue != null) {
            	//Excel中不显示的字段map
                judgeStrMap.put(excel.judgeStr(), judgesStrValue);
                continue;
            }
            fieldAndExcelList.add(new Object[]{field, excel});
            titleMergeCellNum += 1;
        	}
		}

创建工作表并导入数据

/**
     * 导出excel
     * @param request
     * @param response
     * @param dataList
     * @param fileEnum
     * @throws IllegalAccessException
     */
    public void export(HttpServletRequest request,HttpServletResponse response, List<T> dataList,FileName fileEnum) throws IllegalAccessException {
        String sheetName=fileEnum.getSheetName();
        init(request,sheetName);
        //数据导入开始下标
        int rowBeginIndex=0;
        //计算数据导出所需sheet数
        double sheetNum=Math.ceil(dataList.size()/SHEET_MAX_NUM);
        for (int i=0;i<=sheetNum;i++){
            if(i>0){
                sheet=wb.createSheet(sheetName+i);
                rowBeginIndex+=SHEET_MAX_NUM;
            }
            //创建标题
            if(titleMergeCellNum>0){
                sheet.addMergedRegion(new CellRangeAddress(0,0,0, titleMergeCellNum));
            }
            Row titleRow=createRow(0);
            titleRow.setHeight((short)(20*20));
            Cell titleCell=titleRow.createCell(0);
            titleCell.setCellValue(fileEnum.getTitleValue());
            titleCell.setCellStyle(style.get("title"));
            //创建字段行
            Row fieldRow=createRow(1);
            //单元格下标
            int listSize= fieldAndExcelList.size();
            CellStyle fieldStyle=style.get("field");
            for (int i2=0;i2<listSize;i2++){
                Excel excelObj=(Excel) fieldAndExcelList.get(i2)[1];
                //如果字段是不需要导出的,就返回
                if(judgeStrMap.get(excelObj.judgeStr())!=null){
                    continue;
                }
                //创建单元格
                Cell fieldCell=createCell(fieldRow,i2,fieldStyle);
                //向单元格内填充字段信息
                fieldCell.setCellValue(excelObj.name());
                //设置列宽
                sheet.setColumnWidth(i2,excelObj.width()*256);
            }
            //开始导入数据
            importData(dataList,rowBeginIndex);
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        OutputStream out=null;
        try {
            wb.write(os);
            byte[] bytes = os.toByteArray();
            response.addHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileEnum.getFileName()+".xlsx", "utf-8"));
            out= response.getOutputStream();
            out.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                wb.close();
                if(out!=null){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

接下来就是填充数据的方法

/**
     * 导入数据到excel中
     * @param dataList
     * @param beginIndex
     * @throws IllegalAccessException
     */
    private void importData(List<T> dataList,int beginIndex) throws IllegalAccessException {
        int dataListSize=dataList.size();
        //获取数据单元格样式
        CellStyle dataStyle=style.get("data");
        //数据行开始下标
        int rowIndex=DATA_BEGIN_INDEX;
        //字段集合长度(用来创建列)
        int fieldListLength= fieldAndExcelList.size();
        for (int i=beginIndex;i<dataListSize && i<beginIndex+SHEET_MAX_NUM;i++,rowIndex++){
            T dataObj=dataList.get(i);
            Row row=createRow(rowIndex);
            for (int i2=0;i2<fieldListLength;i2++){
            	//获取字段信息
                Field field=(Field) fieldAndExcelList.get(i2)[0];
                //设置字段的访问权限
                field.setAccessible(true);
                //获取对象内的这个字段信息
                Object object=field.get(dataObj);
                //创建单元格
                Cell cell=createCell(row,i2,dataStyle);
                //给单元格填充值
                setCellValue(cell,object,(Excel) fieldAndExcelList.get(i2)[1]);
            }
        }
    }

导出方法到这里就结束了,接下来便是导入的方法,写完了导出方法后,导入方法写起来就相对比较容易了,导出方法如下

/**
     * 导入excel
     * @param inputStream 输入流
     * @param rowBeginIndex 读取数据的开始下标
     * @return 结果集
     */
    public List<T>importExcel(InputStream inputStream,int rowBeginIndex,int sheetIndex){

        List<T> res=new ArrayList<>();
        try {
        	//将流转换为工作簿
            wb=WorkbookFactory.create(inputStream);
            //获取工作簿的工作表
            Sheet sheet=wb.getSheetAt(sheetIndex);
            //获取有效数据行数
            int rowNum=sheet.getPhysicalNumberOfRows();
            if(rowNum<=rowBeginIndex){
                throw new NullPointerException("数据不能为空");
            }
            //获取字段标题的一行数据
            Row headRow=sheet.getRow(FIELD_ROW_INDEX);
            //获取有效列数
            int headCellNum=headRow.getPhysicalNumberOfCells();
            //标题map,用来存储字段标题内容和下标
            Map<String,Integer> headMap=new HashMap<>(headCellNum);
            //循环单元格,并取出字段名
            for (int i=0;i<headCellNum;i++){
                Cell headCell=headRow.getCell(i);
                headMap.put(String.valueOf(headCell.getStringCellValue()),i);
            }
            Field [] fields=clazz.getDeclaredFields();
            //创建字段map,key为字段标题的下标
            Map<Integer,Field>fieldMap=new HashMap<>(headCellNum);
            for (Field field:fields) {
                Excel excel=field.getAnnotation(Excel.class);
                if(excel.excelType()==ExcelType.IS_EXPORT){
                    continue;
                }
                //设置私有属性可以访问
                field.setAccessible(true);
                if (!headMap.containsKey(excel.name())){
                    continue;
                }
                fieldMap.put(headMap.get(excel.name()),field);
            }
            //开始获取工作表中的数据
            for (int i=rowBeginIndex;i<rowNum;i++){
            	//获取对应的行
                Row row=sheet.getRow(i);
                //创建实体对象
                T t=clazz.newInstance();
                //循环存储字段的map,用map的key来获取对应的单元格内容
                for (Map.Entry<Integer,Field>entry:fieldMap.entrySet()){
                    Cell cell=row.getCell(entry.getKey());
                    setEntityFieldValue(t,cell,entry.getValue());
                }
                res.add(t);
            }
        } catch (IOException  | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return res;
    }
/**
     * 给实体字段赋值
     * @param t
     * @param cell
     * @param field
     */
    private void setEntityFieldValue(T t, Cell cell, Field field) {
        Object value=getCellValue(cell,field);
        //利用反射来给实体字段赋值
        ReflectUtils.invokeSetter(t,field.getName(),value);
    }

接下来便是获取单元格内的内容方法

/**
     * 获取单元格值
     * @param cell
     * @param field
     * @return
     */
    private Object getCellValue(Cell cell, Field field){
        Class<?> fieldType=field.getType();
        Object value;
        CellType cellType=cell.getCellType();
        //判断单元格类型,目前只实现了数值和String类型的获取
        //如果是数值型的话
        if(cellType==CellType.NUMERIC){
            if(Double.class==fieldType||Float.class==fieldType||BigDecimal.class==fieldType){
                if(Double.class==fieldType){
                    value=cell.getNumericCellValue();
                }else if(Float.class==fieldType){
                    value=Float.valueOf(String.valueOf(cell.getNumericCellValue()));
                }else{
                    value=new BigDecimal(String.valueOf(cell.getNumericCellValue()));
                }
            }else{
                value=cell.getNumericCellValue();
                String val=new DecimalFormat("0").format(value);
                if(Integer.class==fieldType){
                    value=Integer.valueOf(val);
                }else if(Long.class==fieldType){
                    value=Long.valueOf(val);
                }else if(Short.class==fieldType){
                    value=Short.valueOf(val);
                }
            }
         //如果是String型的话
        }else if(cellType==CellType.STRING){
            if(String.class==fieldType){
                value=cell.getStringCellValue();
            }else if(LocalDate.class==fieldType){
                Excel excel=field.getAnnotation(Excel.class);
                value=DateUtil.strToLocalDate(cell.getStringCellValue(),excel.dateFormat());
            }else if(LocalDateTime.class==fieldType){
                Excel excel=field.getAnnotation(Excel.class);
                value=DateUtil.strToLocalDateTime(cell.getStringCellValue(),excel.dateTimeFormat());
            }else {
                Excel excel=field.getAnnotation(Excel.class);
                String val=cell.getStringCellValue();
                if("".equals(excel.valueConvert())){
                    value=val;
                }else {
                    value=valueConvert(val,excel,false);
                }
            }
        }else {
            value=null;
        }
        return value;
    }

以上便是代码的大概实现思路了,需要源码的可以去我的GitHub上拉取源码,下面是项目地址:

https://github.com/linFeng185/excel

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值