在Java项目中经常会用到Excel相关的功能(导入导出),而实现方式也只有Apache的POI(繁琐且慢),最近发现了阿里开源的EasyExcel,主打简单省内存

依赖

1
2
3
4
5
6
 <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.3</version>
</dependency>

准备

本篇将利用一个用户对象演示导入导出

准备实体类

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 7108437244995175240L;
@ExcelProperty("ID")// 定义标题
private Long id;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("邮箱")
private String email;
}

写(导出)

模拟数据

1
2
3
4
5
6
7
public List<User> getData(){
List<User> list = new ArrayList<>();
for (int i = 1; i < 21; i++) {
list.add(new User((long)i,"王"+i,i+"@gmail.com"));
}
return list;
}

简单写

无特殊需求,将对象中字段全部导出

注意:无论字段是否有@ExcelProperty注解,都会导出,默认标题为字段名;

如果需要忽略字段可以使用@ExcelIgnore注解忽略

1
2
3
public void simpleWrite(String fileName){
EasyExcel.write(fileName, User.class).sheet("用户导出数据").doWrite(this.getData());
}

忽略某些字段

1
2
3
4
5
6
7
8
public void excludeWrite(String fileName,String... excludeNames){
Set<String> excludeColumnFiledNames = new HashSet<String>(Arrays.asList(excludeNames));
EasyExcel
.write(fileName, User.class)
.excludeColumnFiledNames(excludeColumnFiledNames) // 设置忽略字段
.sheet("用户导出数据-忽略"+String.join(",",excludeColumnFiledNames)+"字段")
.doWrite(this.getData());
}

仅导出某些字段

1
2
3
4
5
6
7
8
public void includeWrite(String fileName,String... includeNames){
Set<String> includeColumnFiledNames = new HashSet<String>(Arrays.asList(includeNames));
EasyExcel
.write(fileName, User.class)
.includeColumnFiledNames(includeColumnFiledNames) // 设置包含的字段
.sheet("用户导出数据-仅导出"+String.join(",",includeColumnFiledNames)+"字段")
.doWrite(this.getData());
}

格式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
public class ConverterData {
/**
* 我想所有的 字符串起前面加上"自定义:"三个字
*/
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
/**
* 我想写到excel 用年月日的格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
/**
* 我想写到excel 用百分比表示
*/
@NumberFormat("#.##%")
@ExcelProperty(value = "数字标题")
private Double doubleData;
}

添加图片(不推荐使用,太慢)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageData {
private File file;
private InputStream inputStream;
/**
* 如果string类型 必须指定转换器,string默认转换成string
*/
@ExcelProperty(converter = StringImageConverter.class)
private String string;
private byte[] byteArray;
/**
* 根据url导出
*/
private URL url;
}

读(导入)

这里的读取使用了监听器,读取不返回数据,读取后的业务操作要放到监听器里

定义监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 用户读取信息监听器(监听读取Excel行为)
* <br/>
* @author KThirty
* @since 2020/5/15 16:41
*/
public class UserImportListener extends AnalysisEventListener<User> {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里可以解释为什么不能使用Spring管理,因为spring管理Bean是单例的,里面的对象也就会共享了,这里的importData就不一定是本次导入的数据了
private List<User> importData = new ArrayList<>();
/**
* 这个每一条数据解析都会来调用
* <br/>
* @param user 本条读取出来的数据
* @param context AnalysisContext
* @return void
* @author KThirty
* @since 2020/5/15 16:43
*/
@Override
public void invoke(User user, AnalysisContext context) {
System.out.println("读取到数据"+user);
importData.add(user);
}

/**
* 所有数据解析完成了
* <br/>
* @param context 上下文
* @return void
* @author KThirty
* @since 2020/5/15 16:48
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("本次一共导入了"+importData.size()+"条数据");
}
}

填充

准备模板

解释

  • {.name} 表示循环获取对象的name字段,或者map中key为name的value
  • {name} 只获取一次

代码

1
2
3
4
5
public void simpleFill(String templateFileName,String fileName){
EasyExcel.write(fileName).withTemplate(templateFileName) // 模板文件
.sheet() // 指定sheet页
.doFill(new Write().getData()); // 传入数据
}

填充后效果

附录