Java注解生成Excel_Java:自定义注解+反射实现导入导出Excel文档

本文介绍了如何使用Java注解和反射来高效处理Excel导入导出。通过自定义注解标记Java Bean字段,对应Excel文档的单元格,实现了灵活地解析和生成Excel文件。内容涵盖表单式和表格式模板的处理,以及具体的实现代码示例。
摘要由CSDN通过智能技术生成

问题背景

最近遇到的需求:用户填写平台提供的模板文件(sample.xlsx),导入到平台中,代替填写表单/表格的动作。用户也可以将填好的表单/表格导出成Excel文件,便于以文档的形式存档或传输。

问题分析

从上述需求得出,这就是一个Excel文档与Java Bean对象互相转换的问题。

Excel文件的读写、操作,可以使用Apache Poi或其他开源库,在此不多说。主要问题是,当模板文件内容较为复杂,或者需要处理多个模板时,怎样能快速解析出文档的内容,与Java Bean的字段对应上呢?

应用Java反射的原理,使用自定义注解 + 泛型,很容易实现灵活适配多个模板,并能快速支持模板的修改,便于扩展和维护。

模板文件分析

分析模板文件,我发现可以将模板分为两种。

表单式

accd5e5b4747

表单式

内容行标、列标均确定,例如该表格B1固定为姓名的值,将内容解析成单个JavaBean对象。

表格式

accd5e5b4747

表格式

内容列标固定,例如A列均为学号,应将除去表头外的每一行解析成一个JavaBean 对象,返回Java Bean对象的列表。

分析完毕,发现Java Bean 对象的某个字段的值与Excel文档单元格内容的对应关系就是行标 + 列标,那么我们只需要记录这个坐标,就能实现转换。

使用在字段上加注解的方式,简明易懂,并且很容易维护。下面给出实现代码。

实现

1、定义注解类

@Target({ElementType.FIELD})

@Retention(RetentionPolicy.RUNTIME)

public @interface ExcelAnnotation {

/**

* 中文名称 label

*/

String cnName() default "";

/**

* 英文名称 对应到JavaClass上的类名

*/

String enName() default "";

/**

*

文件中的行标 - 从0开始计数

*/

int rowIndex() default -1;

int sheetIndex() default -1;

/**

*

* 文件中的列标 - 从0开始计数

*/

int columnIndex() default -1;

}

注解用来说明字段对应的单元格工作簿序号、行标、列标等信息,以及

2、定义Java Bean

表单式对应的对象

@Data

class Person{

@ExcelAnnotation(rowIndex = 0,columnIndex = 1,cnName = "姓名")

private String name;

@ExcelAnnotation(rowIndex = 2,columnIndex = 1,cnName = "电话号码")

private String phoneNum;

...

}

表格式对应的对象

只需要定义列标,不需要定义行标

@Data

class Student{

@ExcelAnnotation(columnIndex = 0,cnName = "学号")

private String number;

@ExcelAnnotation(columnIndex = 1,cnName = "姓名")

private String name;

@ExcelAnnotation(rowIndex = 2,columnIndex = 1,cnName = "电话号码")

private String phoneNum;

}

3、工具类实现写入和写出

定义Excel操作的工具类

ExcelUtils.java

@Log4j2

public class ExcelUtil {

/**

* 解析 -按照表单解析 - 所有内容占据并且仅占据一个固定单元格

* @param sheet 表单

* @param clazz 泛型的类型

* @param 泛型

* @return 泛型实例

* @throws IllegalAccessException

* @throws InstantiationException

*/

public static E analysisExcelSheetAsForm(Sheet sheet, Class clazz) throws IllegalAccessException, InstantiationException {

// 获取最大行数

int rowNum = sheet.getPhysicalNumberOfRows();

Map map = new HashMap<>();

// 获取开始行(0 + bgnIgnore)

Row titleRow = sheet.getRow(0);

if (null == titleRow) {

return null;

}

// 获取明细列数

int colNum = titleRow.getPhysicalNumberOfCells();

//

int rowBgn = 0;

// 遍历明细

for (int rowIndex = rowBgn; rowIndex < rowNum; rowIndex++) {

// 行信息

Row row = sheet.getRow(rowIndex);

if (row != null) {

// 遍历行并获取到返回值

for (int cellIndex = 0; cellIndex < colNum; cellIndex++) {

Cell cell = row.getCell(cellIndex);

// 位置为 行号 + 列号

String position = rowIndex + "_" +cellIndex;

// 数据

Object cellData = ExcelUtil.getCellFormatValue(cell);

// 将数据存储到Map中

map.put(position, cellData);

}

} else {

break;

}

}

E model = clazz.newInstance();

// 获取类字段

Field[] fields = model.getClass().getDeclaredFields();

for (Field field : fields) {

// 获取字段上的注解

Annotation[] annotations = field.getAnnotations();

if (annotations.length == 0) {

continue;

}

for (Annotation an : annotations) {

field.setAccessible(true);

// 若扫描到ExcelAnnotation注解

if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {

// 获取指定类型注解

ExcelAnnotation column = field.getAnnotation(ExcelAnnotation.class);

String mappedKey = column.rowIndex() + "_" + column.columnIndex();

// 获取model属性的类型

Class> modelType = field.getType();

// 获取map中的数据

Object value = map.get(mappedKey);

// 匹配字段类型

if (null != value) {

// 获取map中存主的字段的类型

Class> cellType = value.getClass();

// 处理int类型不匹配问题

if (cellType == String.class && (modelType == int.class || modelType == Integer.class)) {

value = Integer.valueOf(value.toString());

}

// 处理double/bigDecimal类型不匹配问题

if (cellType == String.class && modelType == BigDecimal.class) {

// 不使用bigDecimal(double),否则bigDecimal(0.1)有惊喜

value = new BigDecimal(value.toString());

}

if (cellType == String.class && modelType == double.class) {

value = Double.valueOf(value.toString());

}

// 处理String类型不匹配问题

if (cellType != String.class && modelType == String.class) {

value = value.toString();

}

}

field.set(model, value);

}

}

field.setAccessible(false);

}

return model;

}

/**

* 解析Excel表格内容 - 按照表格解析

* @param sheet

* @param ignoreRowNum 忽略的行数(表头)

* @param

* @return

*/

public static List analysisExcelSheetAsTable(Sheet sheet,int ignoreRowNum,Class clazz) {

ArrayList rowContent = new ArrayList<>();

TreeMap writeMethodTreeMap = new TreeMap<>();

// 从忽略的表头开始读

for (int i = ignoreRowNum; i < sheet.getPhysicalNumberOfRows(); i++) {

rowContent.add(sheet.getRow(i));

}

for (Field field : clazz.getDeclaredFields()) {

// 获取字段上的注解

Annotation[] annotations = field.getAnnotations();

if (annotations.length == 0) {

continue;

}

for (Annotation an : annotations) {

// 若扫描到ExcelAnnotation注解

if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {

// 获取指定类型注解

ExcelAnnotation excelAnnotation = field.getAnnotation(ExcelAnnotation.class);

try {

// 获取该字段的method方法

PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);

writeMethodTreeMap.put(excelAnnotation.columnIndex(), pd.getWriteMethod());

} catch (IntrospectionException e) {

e.printStackTrace();

}

}

}

}

DataFormatter dataFormatter = new DataFormatter();

List resultList = new ArrayList<>();

for (Row row : rowContent) {

String rowValue = dataFormatter.formatCellValue(row.getCell(0));

try {

T model = clazz.newInstance();

if (StringUtils.isNotBlank(rowValue)) {

// 遍历格子

int i = 0;

for (Cell cell : row) {

if (!writeMethodTreeMap.containsKey(i)) {

i++;

continue;

}

String value = getCellFormatValue(cell).toString();

writeMethodTreeMap.get(i).invoke(model, value);

i++;

}

resultList.add(model);

}

} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {

e.printStackTrace();

}

}

return resultList;

}

/**

* 读取Cell

*

* @param cell

* @return

*/

public static Object getCellFormatValue(Cell cell)

{

Object cellValue;

//判断cell类型

switch (cell.getCellTypeEnum())

{

case NUMERIC:

{

cellValue = cell.getNumericCellValue();

break;

}

case FORMULA:

{

//判断cell是否为日期格式

if (DateUtil.isCellDateFormatted(cell))

{

//转换为日期格式YYYY-mm-dd

cellValue = cell.getDateCellValue();

}

else

{

//数字

cellValue = cell.getNumericCellValue();

}

break;

}

case STRING:

{

cellValue = cell.getRichStringCellValue().getString();

break;

}

default:

cellValue = "";

}

return cellValue;

}

/**

*向 Excel 文件中写入

*/

public static void writeToWorkbookAsForm(T model, Sheet sheet){

// 若传入参数有的为空,直接返回

if(model == null || sheet == null){

return;

}

// 获取类字段

Field[] fields = model.getClass().getDeclaredFields();

for (Field field : fields) {

// 获取字段上的注解

Annotation[] annotations = field.getAnnotations();

if (annotations.length == 0) {

continue;

}

for (Annotation an : annotations) {

field.setAccessible(true);

// 若扫描到ExcelAnnotation注解

if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {

// 获取指定类型注解

ExcelAnnotation column = field.getAnnotation(ExcelAnnotation.class);

try {

String value = (String) field.get(model);

writeToCell(sheet,column.rowIndex(),column.columnIndex(),value);

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

field.setAccessible(false);

}

}

/**

* 读入类型 * * @param tIterable

* @param sheet 表单

* @param ignoredRowNum 忽略的表头行数

* @param 泛型类型

*/

public static void writeToWorkbookAsTable(Iterable tIterable, Sheet sheet,int ignoredRowNum,Class clazz){

// 若传入参数有的为空,直接返回

if(tIterable == null || sheet == null){

return;

}

// 获取类字段

Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {

// 获取字段上的注解

Annotation[] annotations = field.getAnnotations();

if (annotations.length == 0) {

continue;

}

for (Annotation an : annotations) {

field.setAccessible(true);

// 若扫描到ExcelAnnotation注解

if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {

// 获取指定类型注解

ExcelAnnotation column = field.getAnnotation(ExcelAnnotation.class);

// 行号

AtomicInteger rowIndex = new AtomicInteger(ignoredRowNum);

// 遍历赋值按列写入

tIterable.forEach(item -> {

try {

String value= (String) field.get(item);

writeToCell(sheet, rowIndex.get(),column.columnIndex(),value);

} catch (IllegalAccessException e) {

e.printStackTrace();

}

rowIndex.getAndIncrement();

});

}

}

field.setAccessible(false);

}

}

/**

* 向指定格子写入

* @param rowIndex

* @param columnIndex

*/

static void writeToCell(Sheet sheet, int rowIndex, int columnIndex, String cellValue){

Row row = sheet.getRow(rowIndex) == null?sheet.createRow(rowIndex):sheet.getRow(rowIndex);

Cell cell = row.getCell(columnIndex) == null?row.getCell(columnIndex):row.createCell(columnIndex);

cell.setCellValue(cellValue);

}

}

4、调用

在业务类(Service)中调用

调用仅为示范

// 导入

public void importExcelFile(InputStream inputStream){

try (Workbook workbook = WorkbookFactory.create(inputStream)) {

Person person= ExcelUtil.analysisExcelSheetAsForm(workbook.getSheetAt(0),Person.class);

List students= ExcelUtil.analysisExcelSheetAsTable(workbook.getSheetAt(1),1,Student.class);

// 仅示范调用方式,可自行返回

} catch (Exception ex) {

log.error("Excel解析失败!",ex);

throw new BusinessException("Excel解析失败!");

}

}

//导出

public void exportExcelFile(List students,Person person,HttpServletResponse httpServletResponse){

//1、读取excel模板文件,作为本次下载的模板

try(InputStream inputStream = new FileInputStream(templatePath);

Workbook workbook = WorkbookFactory.create(inputStream)){

httpServletResponse.setContentType("application/octet-stream");

httpServletResponse.setHeader("Content-Disposition", "attachment;filename=test.xlsx");

// 2.根据查询到的内容,填充Excel表格内容

ExcelUtil.writeToWorkbookAsForm(person,workbook.getSheetAt(0));

ExcelUtil.writeToWorkbookAsTable(students,workbook.getSheetAt(1,1,Student.class);

workbook.write(httpServletResponse.getOutputStream());

} catch (IOException | InvalidFormatException e) {

e.printStackTrace();

}

}

这样就完成啦。

对于写入Excel和读取Excel的一些格式、编码/解码方式,我们可以放在通用配置里,也可以在注解类中增加项目来应对特殊需求。

总结

本篇涉及知识

1、泛型

2、自定义注解 + Java反射

3、Apache Poi的使用

为什么使用泛型?

1、将数据类型和算法进行剥离是一种很常用的设计思路,可以帮助我们更好地开发出通用方法。

2、使用泛型(而不是包容万物的Object类型)使代码更为可读,并能规避编译错误,还可以对传入类型的上界、下界进行规定。(super/extends)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值