【纯干货】SSM 实现将数据导出为 Excel 表格(利用反射实现类型通配)

0. 这里假设 SSM 环境已经搭建完成

1. 需要三个依赖包

<!-- 导出为 Excel -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.0.0</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.0.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.10</version>
</dependency>

2. 核心工具类

目标是传入泛型数据列表 List dataList 实现将所有类型输出为 excel 表格,而不需要自己手动指定属性,所以对类的操作必不可少

这里利用反射达到了类的成员变量名
以及得到成员变量名和其值的映射关系(使用 Map,键是成员变量名 String ,值是成员变量值 Object)

ClassUtils.java

package com.iceclean.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : Ice'Clean
 * @date : 2021-08-05
 *
 * 对类的操作
 */
public class ClassUtils {

    /**
     * 通过类型返回对应的属性列表
     * @param target 目标类型
     * @return 属性列表
     */
    public static List<String> getFields(Class<?> target) {
        // 获取所有成员变量名称
        List<String> fieldList = new ArrayList<>();
        Field[] declaredFields = target.getDeclaredFields();

        // 放入 List 集合中
        for(Field field : declaredFields) {
            fieldList.add(field.getName());
        }

        return  fieldList;
    }

    /**
     * 获取泛型所有的成员及其值的映射
     * @param data 泛型对象
     * @param <T> 泛型
     * @return 成员变量名和成员变量值的映射
     */
    public static <T> Map<String, Object> getFieldOfValue(T data) throws IllegalAccessException {
        // 获取所有成员变量,并建立一个 Map(大小为成员变量的个数)来存放名字和值的对应关系
        Field[] declaredFields = data.getClass().getDeclaredFields();
        Map<String, Object> fieldMap = new HashMap<>(declaredFields.length);

        // 获取每个成员属性对应的值,并映射
        for (Field field : declaredFields) {
            // 允许访问私有变量的值
            field.setAccessible(true);

            // 获取成员变量的值,并放入 map 中
            fieldMap.put(field.getName(), field.get(data));
        }

        return fieldMap;
    }

}

3. 核心代码

/**
 * 将列表数据导出为 Excel 表格
 * @param dataList 列表数据
 * @param out 输出源
 * @param <T> 数据的类型
 */
public static <T> void exportExcel(List<T> dataList, ServletOutputStream out) throws IllegalAccessException, IOException {
    // 空集合直接返回
    if (dataList.isEmpty()) {
        return;
    }

    // 获取泛型 T 的所有成员变量
    List<String> fieldList = ClassUtils.getFields(dataList.get(0).getClass());

    // 建立一个 Excel 文件,并在文件中创建一个表单
    HSSFWorkbook workbook = new HSSFWorkbook();
    HSSFSheet sheet = workbook.createSheet();

    // 创建行(表头)
    HSSFRow row = sheet.createRow(0);

    // 创建单元格,并设置表头居中
    HSSFCellStyle hssfCellStyle = workbook.createCellStyle();
    hssfCellStyle.setAlignment(HorizontalAlignment.CENTER);

    // 循环设置列名
    HSSFCell hssfCell;
    for (int i = 0; i < fieldList.size(); i++) {
        hssfCell = row.createCell(i);
        hssfCell.setCellValue(fieldList.get(i));
        hssfCell.setCellStyle(hssfCellStyle);
    }

    // 为每一行添加数据
    Map<String, Object> dataMap;
    for (int i = 0; i < dataList.size(); i++) {
        // 添加新的一行
        row = sheet.createRow(i + 1);

        // 获取泛型对象 T 的成员变量和值的对应关系
        dataMap = ClassUtils.getFieldOfValue(dataList.get(i));

        // 将数据写入该行
        for (int j = 0; j < fieldList.size(); j++) {
            // 先通过 list 获取成员变量名,再通过 map 获取对应的值,转化为 String 之后放入表格中
            row.createCell(j).setCellValue("" + dataMap.get(fieldList.get(j)));
        }
    }

    // 将数据输出,刷出,关闭
    workbook.write(out);
    out.flush();;
    out.close();
}

同样是三步完成~


接下来是简单的使用演示
这里 Controller 层为了简化演示,使用了 Get 请求,制造数据也只起演示作用,可以自行替换

其中使用的 User 包含了 userId(Integer)userName(String)和 userPass(String)三个属性

=== Controller===

@GetMapping("/excel")
@ResponseBody
public void exportExcel(HttpServletResponse response) throws IOException, IllegalAccessException {
    // 设置 ContentType 和返回头
    response.setContentType("application/binary;charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode("导出"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +".xls", "UTF-8"));

    //获得输出流
    ServletOutputStream out = response.getOutputStream();

    // 制造数据
    List<User> userList = new ArrayList<>();
    userList.add(new User(1, "iceclean", "ice123"));
    userList.add(new User(2, "len", "len123"));
    userList.add(new User(3, "rin", "rin123"));
    userList.add(new User(4, "rex", "rex123"));
    userList.add(new User(5, "alsie", "als123"));

    // 输出 excel 表格数据
    fileService.exportExcel(userList, out);
}

=== Service===

public <T> void exportExcel(List<T> dataList, ServletOutputStream out) throws IOException, IllegalAccessException {
    FileUtils.exportExcel(dataList, out);
}

Postman 上的演示:
什么值也不用传,可以看到返回的是乱码(其实是二进制流)
在这里插入图片描述
所以这里需要放到浏览器中去执行(可以用前端写个 post 方法,这里单纯使用 get 直接测试)

使用浏览器直接访问的效果:
将直接弹出个下载框
在这里插入图片描述
打开看里面的数据
在这里插入图片描述
任务圆满完成~~

▲ 与之相关的还有文件导出篇,可以看看这篇【纯干货】SSM 实现文件上传


2021-8-10 更新

对以上方法的不足之处作了改进,包括以下两点:

  • 当字段为空时,表格中直接显示为 null,可以改进为人为指定 null 的代替值
  • 表格中的列名直接为属性英文名,可以改进为人为指定其对应的中文名称

对空值的改进

简单的改进的话,只需要找到工具类 ClassUtils.java 中的 getFieldOfValue() 方法,对里边的值作判断即可
也可以将默认值作为字段提取出来成为形参,供调用者输入,这里不作演示了

// 获取每个成员属性对应的值,并映射
for (Field field : declaredFields) {
    // 允许访问私有变量的值
    field.setAccessible(true);

    // 获取属性值,并在其为空时赋予默认值
    String fieldData = field.get(data).toString();
    fieldData = fieldData == null ? "": fieldData;

    // 获取成员变量的值,并放入 map 中
    fieldMap.put(field.getName(), fieldData);
}

对列名的改进

① 首先添加自定义注解:
其中元注解 Target 表示注解使用在什么地方(这里设置为成员变量),Retention 设置注解在什么时候可以存在(这里设置为在运行期间可以被反射读取到)

/**
 * @author : Ice'Clean
 * @date : 2021-08-10
 *
 * 为实体类的成员变量起名字
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldName {
    String value() default "名字";
}

② 然后在实体类中使用该注解
自定义属性名对应的中文名称

/**
 * @author : Ice'Clean
 * @date : 2021-08-05
 */
public class User {

    @FieldName("用户 Id")
    private Integer userId;

    @FieldName("用户名")
    private String userName;

    @FieldName("用户密码")
    private String userPass;

	//... 省略
}

③ 在 ClassUtils.java 工具类中添加以下方法

/**
 * 通过类型返回对应的属性名称列表
 * 名称为该属性上的 FieldName(自定义注解) 的值
 * @param target 目标类型
 * @return 属性对应的中文名称列表
 */
public static List<String> getFieldNames(Class<?> target) {
    // 获取所有成员变量名称
    List<String> fieldList = new ArrayList<>();
    Field[] declaredFields = target.getDeclaredFields();

    // 放入 List 集合中
    for(Field field : declaredFields) {
        // 获取该成员变量注解 FieldName 的值,即其对应的中文名称
        // 这里通过反射,取出了属性中的 value 值,即属性对象的中文名称
        fieldList.add(field.getAnnotation(FieldName.class).value());
    }

    return  fieldList;
}

④ 对核心代码进行改进
改动的地方很简单,增加一个中文名称列表(从上面方法中获得),将列名的填充值改成由该列表填充即可

/**
 * 将列表数据导出为 Excel 表格
 * @param dataList 列表数据
 * @param out 输出源
 * @param <T> 数据的类型
 */
public static <T> void exportExcel(List<T> dataList, ServletOutputStream out) throws IllegalAccessException, IOException {
    // 空集合直接返回
    if (dataList.isEmpty()) {
        return;
    }

    // 获取泛型 T 的所有成员变量
    // 这里的第一种是获取原属性名,第二种是获取属性对应的注解 FieldName 的值,即对应的中文名称
    List<String> fieldList = ClassUtils.getFields(dataList.get(0).getClass());
    List<String> fieldNameList = ClassUtils.getFieldNames(dataList.get(0).getClass());

    // 建立一个 Excel 文件,并在文件中创建一个表单
    HSSFWorkbook workbook = new HSSFWorkbook();
    HSSFSheet sheet = workbook.createSheet();

    // 创建行(表头)
    HSSFRow row = sheet.createRow(0);

    // 创建单元格,并设置表头居中
    HSSFCellStyle hssfCellStyle = workbook.createCellStyle();
    hssfCellStyle.setAlignment(HorizontalAlignment.CENTER);

    // 循环设置列名(这里使用注解对应的中文名)
    HSSFCell hssfCell;
    for (int i = 0; i < fieldNameList.size(); i++) {
        hssfCell = row.createCell(i);
        hssfCell.setCellValue(fieldNameList.get(i));
        hssfCell.setCellStyle(hssfCellStyle);
    }

    // 为每一行添加数据
    Map<String, Object> dataMap;
    for (int i = 0; i < dataList.size(); i++) {
        // 添加新的一行
        row = sheet.createRow(i + 1);

        // 获取泛型对象 T 的成员变量和值的对应关系
        dataMap = ClassUtils.getFieldOfValue(dataList.get(i));

        // 将数据写入该行
        for (int j = 0; j < fieldList.size(); j++) {
            // 先通过 list 获取成员变量名,再通过 map 获取对应的值,转化为 String 之后放入表格中
            row.createCell(j).setCellValue("" + dataMap.get(fieldList.get(j)));
        }
    }

    // 将数据输出,刷出,关闭
    workbook.write(out);
    out.flush();;
    out.close();
}

⑤ 最终效果:
在这里插入图片描述


晨露夜集(IceClean)

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
SSM(Spring + SpringMVC + MyBatis)框架可以很方便地实现Excel文件的导入和导出。下面是一个简单的实现流程: 1. 导出Excel文件 - 添加POI依赖,例如: ``` <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> ``` - 创建一个Excel文件,例如: ``` Workbook workbook = new HSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); Row row = sheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue("Hello, world!"); ``` - 将Excel文件写入输出流,例如: ``` response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-disposition", "attachment;filename=test.xls"); OutputStream outputStream = response.getOutputStream(); workbook.write(outputStream); outputStream.flush(); outputStream.close(); ``` 2. 导入Excel文件 - 添加POI依赖,例如: ``` <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> ``` - 读取Excel文件,例如: ``` InputStream inputStream = new FileInputStream("test.xls"); Workbook workbook = new HSSFWorkbook(inputStream); Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(0); Cell cell = row.getCell(0); System.out.println(cell.getStringCellValue()); ``` - 将Excel数据保存到数据库,例如: ``` @Autowired private UserService userService; for (int i = 1; i <= sheet.getLastRowNum(); i++) { row = sheet.getRow(i); User user = new User(); user.setName(row.getCell(0).getStringCellValue()); user.setAge((int) row.getCell(1).getNumericCellValue()); userService.saveUser(user); } ``` 以上是一个简单的实现流程,具体实现方式可以根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒冰小澈IceClean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值