Excel工具类实现——基于poi5.2.3

初衷仅是因为网上的Excel工具类不是太老旧的方法,就是不是很满足我想法,就想要自己搞一个,不过还不支持合并单元格等复杂的操作,后续看看能不能优化,有大神有更好的方法,可以公众号联系我,让我学习学习
原文及源码链接:Excel工具类实现

整体设计

使用方式

// @Sheet 不是必须的, 如果需要设置字体颜色/背景色, 可以使用该注解
@Sheet(bgColor = "#CBCBCB")
public class Demo {

    @SheetColumn(name = "ID")
    public Integer id;

    @SheetColumn(name = "Name")
    public String name;

    @SheetColumn(name = "Description", width = 30) // 宽度接近英文字符数
    public String description;

    // 默认为: yyyy-MM-dd, 根据实际情况设置
    @SheetColumn(name = "Created At", dateFormat = "yyyy-MM-dd HH:mm:ss")
    public LocalDateTime createdAt;

}
//读群数据
ExcelReader<Demo> reader=Excel.createReader(Demo::new);
reader.setEvaluateFormula(true); // 如果需要计算公式, 请设置为 true
List<Demo> list=reader.read(inputStream);
//写入数据
ExcelWriter<Demo> writer=Excel.createWriter(Demo.class);
Demo demo=new Demo();
demo.name="哈哈";
demo.createdAt=LocalDateTime.now();
...
    writer.write(demo);// 也可以是 List
writer.save(new FileOutputStream("D:/a.xlsx"));

需要依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

代码实现

  1. 首先Excel类只定义了读写创建器和部分跟读写关系不大的方法,具体的实现由读写类内部实现,使用起来比较清晰

    /**
     * @author ZeroClian
     * @date 2023-01-10 15:35
     */
    public class Excel {
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
    
        public static <T> ExcelReader<T> createReader(Supplier<T> supplier) {
            return new ExcelReader<>(supplier);
        }
    
        public static <T> ExcelWriter<T> createWriter(Class<T> clazz) {
            return new ExcelWriter<>(clazz);
        }
    
    
        public static Object getCellValue(Cell cell, FormulaEvaluator formulaEvaluator) {
            switch (cell.getCellType()) {
                case ERROR:
                    return ErrorEval.getText(cell.getErrorCellValue());
                case BLANK:
                    return "";
                case BOOLEAN:
                    return cell.getBooleanCellValue();
                case FORMULA:
                    if (formulaEvaluator == null) {
                        return cell.getCellFormula();
                    } else {
                        CellValue cellValue = formulaEvaluator.evaluate(cell);
                        if (cellValue.getCellType() == CellType.NUMERIC) {
                            return cellValue.getNumberValue();
                        }
                        if (cellValue.getCellType() == CellType.STRING) {
                            return cellValue.getStringValue();
                        }
                        if (cellValue.getCellType() == CellType.BOOLEAN) {
                            return cellValue.getBooleanValue();
                        }
                        return cellValue.toString();
                    }
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return cell.getDateCellValue();
                    }
                    return cell.getNumericCellValue();
                case STRING:
                    return cell.getRichStringCellValue().toString();
                default:
                    logger.error("Unknown Cell Type: {}", cell.getCellType());
                    return null;
            }
        }
    
        /**
         * 26进制转换为十进制, 然后减去一(列索引基于0)
         *
         * @param name
         * @return
         */
        public static int toColumnIndex(String name) {
            int n = 0;
            for (int i = name.length() - 1, j = 1; i >= 0; i--, j *= 26) {
                char c = name.charAt(i);
                if (c < 'A' || c > 'Z') return 0;
                n += ((int) c - 64) * j;
            }
            return n - 1;//index要减一
        }
    
        /**
         * @param nm 例如: #FFCCEE
         * @return
         */
        static XSSFColor createColor(String nm) {
            java.awt.Color color = java.awt.Color.decode(nm);
            byte r = (byte) color.getRed();
            byte g = (byte) color.getGreen();
            byte b = (byte) color.getBlue();
            return new XSSFColor(new byte[]{r, g, b});
        }
    
    }
    
  2. 读excel核心就是如何将获取的值与Field字段对应,这里采用的自定义注解的方式,通过excel文件的列名与注解的列名对应即可,之后就是将cell值转换为对应的对象,再为field赋值,注意如果是private修饰的,需要添加field.setAccessible(true)

    /**
     * @author ZeroClian
     * @date 2023-01-10 15:35
     */
    public class ExcelReader<T> {
    
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
        private static final DefaultConversionService convert = new DefaultConversionService();
    
        private final Supplier<T> supplier;
        private boolean evaluateFormula = false;
        private FormulaEvaluator formulaEvaluator = null;
    
        public ExcelReader(Supplier<T> supplier) {
            this.supplier = supplier;
        }
    
        /**
         * 设置自动计算公式
         *
         * @param evaluateFormula true表示执行计算
         */
        public void setEvaluateFormula(boolean evaluateFormula) {
            this.evaluateFormula = evaluateFormula;
        }
    
        /**
         * 读取Excel
         *
         * @param in 输入流
         * @return {@link List<T> objects} 对象列表
         * @throws IOException
         */
        public List<T> read(InputStream in) throws IOException {
            Workbook workbook = WorkbookFactory.create(in);
            if (evaluateFormula) {
                this.formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
            }
            //读取数据
            List<T> list = new ArrayList<>();
            for (Sheet sheet : workbook) {
                List<String> headers = getSheetHeaders(sheet);
                Iterator<Row> rowIterator = sheet.rowIterator();
                while (rowIterator.hasNext()) {
                    Row row = rowIterator.next();
                    if (row.getRowNum() == 0) {
                        continue;
                    }
                    T obj = rowToObject(headers, row, supplier);
                    if (null != obj) {
                        list.add(obj);
                    }
                }
            }
            return list;
        }
    
        /**
         * 获取列名(位于第一行)
         *
         * @param sheet 页
         * @return {@link List<String> headers} 列名列表
         */
        public List<String> getSheetHeaders(Sheet sheet) {
            Row row = sheet.getRow(0);
            List<String> headers = new ArrayList<>();
            for (Cell cell : row) {
                String cellValue = cell.getStringCellValue();
                headers.add(cellValue);
            }
            return headers;
        }
    
        /**
         * 单行转为对象
         *
         * @param headers  列名
         * @param row      行
         * @param supplier 要转换的对象
         * @return {@link Object}
         */
        private T rowToObject(List<String> headers, Row row, Supplier<T> supplier) {
            try {
                T obj = supplier.get();
                //获取字段
                Field[] fields = obj.getClass().getDeclaredFields();
                boolean isEmpty = true;
                for (Field field : fields) {
                    SheetColumn ec = field.getAnnotation(SheetColumn.class);
                    if (null == ec) continue;
                    int columnIndex = headers.indexOf(ec.name());
                    if (columnIndex >= 0) {
                        Cell cell = row.getCell(columnIndex);
                        if (null != cell) {
                            isEmpty = setValue(obj, field, cell, ec.dateFormat());
                        }
                    }
                }
                return isEmpty ? null : obj;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 向对象设置值
         *
         * @param obj        对象
         * @param field      字段
         * @param cell       单元格
         * @param dateFormat 日期格式
         */
        private boolean setValue(T obj, Field field, Cell cell, String dateFormat) throws IllegalAccessException, ParseException {
            Object value = Excel.getCellValue(cell, formulaEvaluator);
            if (null == value) return true;
            boolean emptyValue = ObjectUtils.isEmpty(value);
            Class<?> valueClass = value.getClass();
            Class<?> fieldClass = field.getType();
            field.setAccessible(true);
            if (fieldClass == valueClass) {
                field.set(obj, value);
                return false;
            }
            //java8 日期、时间
            if (Temporal.class.isAssignableFrom(fieldClass)) {
                if (emptyValue) {
                    return true;
                }
                String cellStr = value.toString();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
                if (LocalDateTime.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, LocalDateTime.parse(cellStr, formatter));
                }
                if (LocalDate.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, LocalDate.parse(cellStr, formatter));
                }
                if (OffsetDateTime.class.isAssignableFrom(fieldClass)) {
                    field.set(obj, OffsetDateTime.parse(cellStr, formatter));
                }
            }
            //java.util.Date
            if (Date.class.isAssignableFrom(fieldClass)) {
                if (emptyValue) {
                    return true;
                }
                String cellStr = value.toString();
                SimpleDateFormat format = new SimpleDateFormat(dateFormat);
                Date date = format.parse(cellStr);
                field.set(obj, date);
                return false;
            }
            if (convert.canConvert(valueClass, fieldClass)) {
                Object newValue = ExcelReader.convert.convert(value, fieldClass);
                if (null != newValue) {
                    field.set(obj, newValue);
                }
                return false;
            }
            throw new IllegalArgumentException("表数据类型与类字段类型不一致: " + value.getClass() + " -> " + field.getType());
        }
    }
    
  3. 导出则比较简单,只是将对象转换为对应列的值,跟其他人的区别是,增加了表头的注解,用于简单设置导出的颜色背景样式,

    /**
     * @author ZeroClian
     */
    public class ExcelWriter<T> implements AutoCloseable {
    
        private static final Logger logger = LoggerFactory.getLogger(Excel.class);
    
        private final Class<T> clazz;
        private final SXSSFWorkbook workbook;
        private SXSSFSheet sheet;
        private Map<Field, Header> fieldHeaderMap;
    
        public ExcelWriter(Class<T> clazz) {
            this.clazz = clazz;
            this.workbook = new SXSSFWorkbook(300);
            this.init();
            this.createSheet();
        }
    
        /**
         * 获取字段对应的列
         */
        public void init() {
            Map<Field, Header> map = new LinkedHashMap<>();
            Set<Integer> usedIndex = new HashSet<>();
            Queue<Integer> queue = new LinkedList<>();
            for (Field field : clazz.getDeclaredFields()) {
                SheetColumn ec = field.getAnnotation(SheetColumn.class);
                if (null == ec) continue;
                int columnIndex = -1;
                if (!ec.column().isEmpty()) {
                    columnIndex = Excel.toColumnIndex(ec.column());
                    if (!usedIndex.add(columnIndex)) {
                        logger.warn("重复列编号: {}", ec.column());
                    }
                }
                Header header = new Header(ec.name(), ec.column(), columnIndex, ec.dateFormat(), ec.width());
                map.put(field, header);
                queue.add(queue.size());
            }
            queue.removeAll(usedIndex);
            map.forEach((field, header) -> {
                if (header.getColumnIndex() < 0) {
                    header.setColumnIndex(queue.remove());
                }
            });
            this.fieldHeaderMap = map;
        }
    
        /**
         * 创建Sheet和表头
         */
        public void createSheet() {
            String name = "Sheet1";
            String color = "#000000";
            String bgColor = "#CBCBCB";
            Sheet sheetInfo = clazz.getAnnotation(Sheet.class);
            if (null != sheetInfo) {
                if (!sheetInfo.name().isBlank()) {
                    name = sheetInfo.name();
                }
                color = sheetInfo.color();
                bgColor = sheetInfo.bgColor();
            }
            this.sheet = workbook.createSheet(name);
            //表头
            Row headerRow = sheet.createRow(0);
            CellStyle cellStyle = workbook.createCellStyle();
            cellStyle.setFillForegroundColor(Excel.createColor(bgColor));
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cellStyle.setAlignment(HorizontalAlignment.CENTER);
            XSSFFont font = (XSSFFont) workbook.createFont();
            font.setColor(Excel.createColor(color));
            font.setBold(true);
            cellStyle.setFont(font);
            sheet.trackAllColumnsForAutoSizing();
            fieldHeaderMap.forEach((field, header) -> {
                if (header.getColumnWidth() > 0) {
                    int width = Math.min(16_384, header.getColumnWidth() * 256);
                    sheet.setColumnWidth(header.getColumnIndex(), width);
                } else if (header.getColumnWidth() == 0) {
                    sheet.setColumnHidden(header.getColumnIndex(), true);
                } else {
                    sheet.autoSizeColumn(header.getColumnIndex());
                }
            });
            fieldHeaderMap.forEach((field, header) -> {
                Cell cell = headerRow.createCell(header.getColumnIndex());
                cell.setCellStyle(cellStyle);
                cell.setCellValue(header.getName());
            });
        }
    
        /**
         * 写入单个对象
         *
         * @param obj 对象
         * @throws IOException
         */
        public void write(T obj) throws IOException {
            try {
                if (null == obj) {
                    return;
                }
                Row row = sheet.createRow(sheet.getLastRowNum() + 1);
                for (Map.Entry<Field, Header> entry : fieldHeaderMap.entrySet()) {
                    Cell cell = row.createCell(entry.getValue().getColumnIndex());
                    Field field = entry.getKey();
                    Header header = fieldHeaderMap.get(field);
                    Object cellValue = field.get(obj);
                    String value;
                    if (cellValue instanceof Date date) {
                        value = DateFormatUtils.format(date, header.getDateFormat());
                    } else if (cellValue instanceof Temporal temporal) {
                        value = DateTimeFormatter.ofPattern(header.getDateFormat()).format(temporal);
                    } else {
                        value = Objects.toString(cellValue, "");
                    }
                    cell.setCellValue(value);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    
        public void write(List<T> list) throws IOException {
            for (T obj : list) {
                write(obj);
            }
        }
    
        public void save(OutputStream out) throws Exception {
            workbook.write(out);
            close();
        }
    
        @Override
        public void close() throws Exception {
            this.workbook.close();
        }
    
    }
    
  4. 剩余就是注解的定义,这里我只定义了常用的,可自行修改

    /**
     * Excel表整体格式配置(非必选)
     * 主要用于设置表格格式
     * 修改:字体颜色,背景颜色
     *
     * @author ZeroClian
     * @date 2023-01-06 20:08
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Sheet {
    
        /**
         * 表名
         */
        String name() default "";
    
        /**
         * 字体颜色
         */
        String color() default "#000000";
    
        /**
         * 背景颜色
         */
        String bgColor() default "#CBCBCB";
    }
    
    /**
     * 列注解
     *
     * @author ZeroClian
     * @date 2023-01-06 20:20
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface SheetColumn {
    
        String name() default "";
    
        String column() default "";
    
        int width() default 16;
    
        String dateFormat() default "yyyy-MM-dd";
    }
    
    /**
     * @author Justin
     */
    @Data
    public class Header {
    
        private String name;
        private String column;
        private int columnIndex;
        private String dateFormat;
        private int columnWidth;
        private XSSFColor color;
        private XSSFColor bgColor;
    
        public Header(String name, String column, int columnIndex, String dateFormat, int columnWidth) {
            this.name = name;
            this.column = column;
            this.columnIndex = columnIndex;
            this.dateFormat = dateFormat;
            this.columnWidth = columnWidth;
        }
    }
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
POI(Poor Obfuscation Implementation)是一种用于操作Microsoft Office格式文件(如Excel、Word)的Java API库。接入POI 5.2.3版本是指在Java项目中使用POI库的5.2.3版本。 要将POI 5.2.3接入到项目中,首先需要在项目中引入POI库的相关依赖。可以通过在项目的构建配置文件中添加POI的依赖坐标或者手动下载POI库的JAR文件并导入到项目中。接入POI库之后,就可以在代码中使用POI提供的功能来操作Microsoft Office格式文件了。 例如,如果想要通过POI库来读取Excel文件,可以使用POI提供的类和方法来完成。首先,需要创建一个Workbook对象,然后通过该对象打开Excel文件。接下来,可以根据需要选择要操作的Sheet和Row,通过getCell()方法获取单元格对象,并使用getStringCellValue()方法获取单元格的值。通过这样的方式,就可以实现Excel文件的读取操作。 除了读取Excel文件,POI库还提供了许多其他功能,如写入Excel文件、操作Word文件等。通过POI的丰富功能,可以在Java项目中方便地实现对Microsoft Office格式文件的操作需求。 总结来说,接入POI 5.2.3版本是指在Java项目中使用POI库的5.2.3版本。通过引入POI的相关依赖,开发人员可以在代码中使用POI提供的功能来操作Microsoft Office格式文件,如读写Excel、操作Word等。这样,就可以满足项目中对这些文件格式的操作需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值