自定义注解通过反射实现Excel的导出功能(提供项目源码)


此次项目 demo 实现了通过自定义注解添加到类中的属性上,直接导出即可,通过此注解可以实现简单的excel导出功能,值类型替换:例如:将0替换成女,将1替换成男,将on替换成开启,将off替换成关闭,还支持日期格式的导出,通过 dateFormat 选择执行的日期格式化标准,然后进行导出到 excel 中。

项目源码地址:https://gitee.com/jian_bo_bai/excel-utils

POI 导入依赖

		<!-- apache-poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.1.2</version>
        </dependency>

自定义 @ExcelProperty 注解

package com.bai.demo.annotation;

import java.lang.annotation.*;

/**
 * <p>
 * 自定义注解导出Excel操作
 * <span>@Target 表示此注解可以被添加到实体类属性上</span>
 * <span>@Retention 表示在程序运行时, 可以通过反射读取此注解中的信息</span>
 * <span>@Documented 表示在生成JavaDoc时, 可以将此注解显示出来</span>
 * </p>
 *
 * @author bai
 * @date 2021/1/18 17:50
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelProperty {
    /**
     * <p>标记要导出的excel的列名称</p>
     *
     * @return 默认为 null
     */
    String name() default "";

    /**
     * <p>标记导出excel列的日期格式</p>
     *
     * @return 日期格式
     */
    String dateFormat() default "";

    /**
     * 替换类型
     * <p>
     * 使用说明: <span>如果你的实体类对象sex属性或者status属性实际获取到的值并不是0/1,或者ON/OFF那么就不会进行替换</span>
     * </p>
     *
     * <p>
     * 根据下划线分隔,翻译过来的意思就是将0替换成女,将1替换成男
     * replace = {"0_女", "1_男"}
     * private int sex;
     * 根据下划线分隔,翻译过来的意思就是将ON替换成开启,将OFF替换成关闭
     * replace = {"ON_开启", "OFF_关闭"}
     * private String status;
     * </p>
     *
     * @return 替换后的中文
     */
    String[] replace() default {};
}

自定义 ExcelConstants 常量常用类

package com.bai.demo.util;

/**
 * <p>
 * 自定义Excel中常用常量类
 * </p>
 *
 * @author bai
 * @date 2021/1/18 17:50
 */
public interface ExcelConstants {
    /**
     * <span>常用日期格式</span>
     */
    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String YYYY_MM_DD_CHINESE = "yyyy年MM月dd日";
    public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
    public static final String YYYY_MM_DD_HH_MM_CHINESE = "yyyy年MM月dd日 HH时mm分";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYY_MM_DD_HH_MM_SS__CHINESE = "yyyy年MM月dd日 HH时mm分ss秒";
    public static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String YYYY_MM_DD_HH_MM_SS_SSS__CHINESE = "yyyy年MM月dd日 HH时mm分ss秒SSS毫秒";
    public static final String YYYY_MM_DD_HH_MM_NO_SEPARATOR = "yyyyMMddHHmm";
    public static final String YYYY_MM_DD_HH_MM_SS_NO_SEPARATOR = "yyyyMMddHHmmss";

    /**
     * <span>getter/setter方法</span>
     */
    public static final String METHOD_GET = "get";
    public static final String METHOD_SET = "set";

    /**
     * <span>excel导出的版本后缀 03 xls / 07 xlsx</span>
     */
    public static final String XLS = ".xls";
    public static final String XLSX = ".xlsx";

    /**
     * <span>通过网络请求 http 导出 excel 时 response 响应信息的一些设置规则</span>
     */
    public static final String RESPONSE_CONTENT_TYPE = "application/octet-stream";
    public static final String RESPONSE_CHARACTER_ENCODING_UTF8 = "utf-8";
    public static final String RESPONSE_HEADER_NAME = "Content-disposition";
    public static final String RESPONSE_HEADER_VALUE = "attachment; filename=";
}

自定义Excel操作工具类

package com.bai.demo.util;

import com.bai.demo.annotation.ExcelProperty;
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.xssf.usermodel.XSSFWorkbook;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * <p>
 * 自定义Excel操作工具类
 * </p>
 *
 * @author bai
 * @date 2021/1/18 17:50
 */
public class ExcelUtil {
    /**
     * <p>
     * 自定义导出excel文件方法
     * </p>
     *
     * @param request   http请求
     * @param response  http响应
     * @param data      需要导出的excel数据 list 集合
     * @param clazz     list集合中的泛型类模板
     * @param excelName 导出的 excel 表名称
     */
    public static void exportFile(HttpServletRequest request, HttpServletResponse response, List<?> data, Class<?> clazz, String excelName) throws Exception {
        // 创建工作簿
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 调用导出excel的方法
        ExcelUtil.export(workbook, data, clazz, excelName);
        // 生成文件名
        String fileName = DateTimeFormatter.ofPattern(ExcelConstants.YYYY_MM_DD_HH_MM_SS_NO_SEPARATOR).format(LocalDateTime.now())
                + excelName + ExcelConstants.XLSX;
        // 设置导出响应流规则
        response.setContentType(ExcelConstants.RESPONSE_CONTENT_TYPE);
        response.setCharacterEncoding(ExcelConstants.RESPONSE_CHARACTER_ENCODING_UTF8);
        response.addHeader(ExcelConstants.RESPONSE_HEADER_NAME, ExcelConstants.RESPONSE_HEADER_VALUE
                + ExcelUtil.makeDownloadFileName(request, fileName));
        // 将文件输出
        try (OutputStream stream = response.getOutputStream()) {
            workbook.write(stream);
            stream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>
     * 导出excel实例方法
     * </p>
     *
     * @param list      导出的list集合
     * @param clazz     导出的实体类模板class
     * @param sheetName excel工作表标题名称
     */
    private static void export(XSSFWorkbook workbook, List<?> list, Class<?> clazz, String sheetName) throws Exception {
        Sheet sheet = workbook.createSheet(sheetName); // 创建工作表 sheet
        Row titleRow = sheet.createRow(0); // 创建标题行 titleRow
        Field[] fields = clazz.getDeclaredFields(); // 通过反射获取类模板中包含public,protected,private等属性
        Map<String, Method> methods = ExcelUtil.getMethod(clazz, ExcelConstants.METHOD_GET); // 获取类模板中所有属性的 getter 方法

        // 创建标题行
        for (int i = 0; i < fields.length; i++) {
            ExcelProperty annotation = fields[i].getAnnotation(ExcelProperty.class); // 获取每一个属性上的 ExcelProperty 注解
            ExcelUtil.setCellValues(titleRow, i, annotation.name()); // annotation.name() 就是实体类中属性上 ExcelProperty 注解中的 name 属性
        }

        // 创建普通行
        for (int i = 0; i < list.size(); i++) {
            Row sheetRow = sheet.createRow(i + 1);
            Object targetObj = list.get(i); // 获取到集合中的一个对象
            int currentIndex = 0;
            for (Field field : fields) {
                Method method = methods.get(field.getName()); // 根据属性名称获取到属性 get 方法
                Object value = method.invoke(targetObj); // 通过 Method.invoke(类对象) 来获取到 get 方法返回的 value 值
                ExcelProperty annotation = field.getAnnotation(ExcelProperty.class); // 获取每一个属性上的 ExcelProperty 注解

                // ExcelProperty 中 dateFormat 属性不为空表示此属性需要日期格式化
                if (StringUtils.hasLength(annotation.dateFormat())) {
                    SimpleDateFormat sdf = ExcelUtil.switchDateFormatter(annotation.dateFormat());
                    ExcelUtil.setCellValues(sheetRow, currentIndex, sdf.format(value));
                }
                // ExcelProperty 中 replace 属性不为空表示此属性需要格式转换
                else if (annotation.replace().length > 0) {
                    for (String replaceText : annotation.replace()) {
                        String[] keys = replaceText.split("_"); // 注解自定义规则就是根据下划线进行分隔对象
                        if (keys.length > 2) throw new RuntimeException("replace关键字规格不正确");
                        if (keys[0].equals(value.toString())) {
                            ExcelUtil.setCellValues(sheetRow, currentIndex, keys[1]);
                        }
                    }
                }
                // 默认情况下的 ExcelProperty 只有 name,直接进行普通 excel 导出即可
                else {
                    ExcelUtil.setCellValues(sheetRow, currentIndex, value);
                }
                currentIndex += 1;
            }
        }
    }

    /**
     * <p>
     * 判断属性需要格式化的日期规则
     * </p>
     *
     * @param format 日期格式化公式
     * @return 格式化对象 SimpleDateFormat
     */
    private static SimpleDateFormat switchDateFormatter(String format) {
        SimpleDateFormat sdf;
        switch (format) {
            case ExcelConstants.YYYY_MM_DD:
                sdf = new SimpleDateFormat(ExcelConstants.YYYY_MM_DD);
                break;
            case ExcelConstants.YYYY_MM_DD_HH_MM_SS:
                sdf = new SimpleDateFormat(ExcelConstants.YYYY_MM_DD_HH_MM_SS);
                break;
            case ExcelConstants.YYYY_MM_DD_HH_MM_SS_SSS:
                sdf = new SimpleDateFormat(ExcelConstants.YYYY_MM_DD_HH_MM_SS_SSS);
                break;
            default:
                sdf = new SimpleDateFormat(ExcelConstants.YYYY_MM_DD_HH_MM_SS);
                break;
        }
        return sdf;
    }

    /**
     * <p>
     * 向指定位置的单元格中设置值
     * </p>
     *
     * @param row          第几行
     * @param currentIndex 第几个单元格
     * @param value        添加进单元格的内容
     */
    private static void setCellValues(Row row, Integer currentIndex, Object value) {
        Cell cell = row.createCell(currentIndex);
        // 通过反射判断传入的 value 类型
        switch (value.getClass().getSimpleName()) {
            case "String":
                cell.setCellValue(value.toString());
                break;
            case "Integer":
                cell.setCellValue(Integer.parseInt(value.toString()));
                break;
            case "Float":
                cell.setCellValue(Float.parseFloat(value.toString()));
                break;
            case "Double":
                cell.setCellValue(Double.parseDouble(value.toString()));
                break;
            case "Long":
                cell.setCellValue(Long.parseLong(value.toString()));
                break;
            case "Boolean":
                cell.setCellValue(Boolean.parseBoolean(value.toString()));
                break;
            default:
                cell.setCellValue("");
                break;
        }
    }

    /**
     * <p>
     * 根据 methodKeyWord 来获取指定类模板下的 getter/setter方法
     * 用法例如: Map<String, Method> map = this.getMethod(Student.class, "get");
     * 那么返回值 map 中的值就是 Student 中所有属性的 get方法
     * </p>
     *
     * @param clazz         类模板
     * @param methodKeyWord get就是获取getter方法/set就是获取setter方法
     */
    private static Map<String, Method> getMethod(Class<?> clazz, String methodKeyWord) {
        Map<String, Method> methodMap = new HashMap<String, Method>();
        /* 获取类模板所有属性 */
        Field[] fields = clazz.getDeclaredFields();
        for (Field value : fields) {
            /* 获取属性名并组装方法名称 */
            String fieldName = value.getName();
            /*
             * methodKeyWord = 'get'
             * fieldName = 'name'
             * fieldName.substring(0, 1).toUpperCase() = 'N'
             * fieldName.substring(1) = 'ame()'
             * getMethodName = 'getName()'
             * */
            String getMethodName = methodKeyWord +
                    fieldName.substring(0, 1).toUpperCase() +
                    fieldName.substring(1);
            try {
                Method method = clazz.getMethod(getMethodName);
                /*
                 * 存储内容为: id,getId();
                 * name,getName();
                 * */
                methodMap.put(fieldName, method);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                System.err.println("无法获取字段的方法:" + value.getName());
            }
        }
        return methodMap;
    }

    /**
     * <p>
     * 生成下载文件
     * </p>
     *
     * @param request  http请求
     * @param fileName 文件名称
     */
    private static String makeDownloadFileName(HttpServletRequest request, String fileName) {
        String agent = request.getHeader("User-Agent");
        byte[] bytes = fileName.getBytes(StandardCharsets.UTF_8);
        if (agent.contains("MSIE") || agent.contains("Trident") || agent.contains("Edge")) {
            return new String(bytes, StandardCharsets.UTF_8);
        } else {
            return new String(bytes, StandardCharsets.ISO_8859_1);
        }
    }
}

测试阶段

创建导出 Excel 相关测试的实体类

package com.bai.demo.model;

import com.bai.demo.annotation.ExcelProperty;
import com.bai.demo.util.ExcelConstants;

import java.util.Date;

/**
 * 导出excel相关的测试实体类
 */
public class Student implements java.io.Serializable {
    @ExcelProperty(name = "序号")
    private Integer id;
    @ExcelProperty(name = "名称")
    private String name;
    @ExcelProperty(name = "年龄")
    private Integer age;
    @ExcelProperty(name = "性别", replace = {"0_女", "1_男"})
    private Integer sex;
    @ExcelProperty(name = "生日", dateFormat = ExcelConstants.YYYY_MM_DD_HH_MM_SS)
    private Date birth;
    @ExcelProperty(name = "家庭地址")
    private String address;

    public Student() {
        super();
    }

    public Student(Integer id, String name, Integer age, Integer sex, Date birth, String address) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.birth = birth;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public int getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", birth=" + birth +
                ", address='" + address + '\'' +
                '}';
    }
}

添加 thymeleaf 依赖

		<!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

添加 StudentController 控制器编写导出 excel 接口

package com.bai.demo.controller;

import com.bai.demo.model.Student;
import com.bai.demo.util.ExcelUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Controller
public class StudentController {
    private static final List<Student> students;

    static {
        students = Arrays.asList(
                new Student(1, "张三", 25, 1, new Date(), "上海朱家嘴"),
                new Student(2, "李四", 33, 0, new Date(), "上海青浦"),
                new Student(3, "王五", 48, 1, new Date(), "上海陆家班"),
                new Student(4, "赵六", 19, 3, new Date(), "上海足球场")
        );
    }

    @RequestMapping(value = "/exportStudentExcel")
    public void exportStudentExcel(HttpServletRequest request, HttpServletResponse response) {
        try {
            ExcelUtil.exportFile(request, response, students, Student.class, "学生信息表");
            System.out.println("excel导出成功~");
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("excel导出失败~");
        }
    }

    @RequestMapping(value = {"/", "/index"})
    public ModelAndView goIndex() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }

    @GetMapping(value = "/hello")
    @ResponseBody
    public String hello() {
        return "hello, world";
    }
}

在 resources/templates 资源目录下创建index.html 文件

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<a th:href="@{/exportStudentExcel}">导出学生记录</a>
</body>
</html>

测试

启动项目 访问:localhost:8080/ 进入首页,然后点击 导出学生记录 请求后台接口导出 excel

效果截图

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值