java动态导入导出excel,javassist动态创建类
Java动态解析excel
1、使用的技术
- javassist
Javassist 使 Java 字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库。它使 Java 程序可以在运行时定义新类,并在 JVM 加载它时修改类文件。与其他类似的字节码编辑器不同,Javassist 提供了两个级别的 API:源级别和字节代码级别。如果用户使用源代码级 API,则他们可以在不了解 Java 字节码规范的情况下编辑类文件。整个 API 仅使用 Java 语言的词汇表进行设计。您甚至可以以源文本的形式指定插入的字节码。Javassist 可以即时对其进行编译。另一方面,字节码级别的 API 允许用户像其他编辑器一样直接编辑类文件。
- easyPOI
Easypoi 是一个基于 Apache POI 的 Java 库,极大地简化了 Excel 文件的读写操作。它提供了一套简单易用的 API,使得开发者可以高效地处理 Excel 数据导入导出任务,无需深入学习复杂的 POI 库细节。Easypoi 支持模板导出、数据验证、自定义样式等多种功能,广泛应用于报表生成、数据预处理等场景。
快速入门可以看我的这篇文章:JAVA操作Excel(POI、easyPOI、easyExcel)
2、包导入(基于SpringBoot)
<!--easypoi--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.4.0</version></dependency><!-- javassist easypoi 自带了,可以不用引入 下图有展示 -->
<!-- <dependency>-->
<!-- <groupId>org.javassist</groupId>-->
<!-- <artifactId>javassist</artifactId>-->
<!-- <version>3.25.0-GA</version>-->
<!-- </dependency>--><!-- easypoi 的 javassist 如果和其他包的冲突了,可以使用以下代码排除掉该包的 javassist 下图有展示 -->
<!-- <dependency>-->
<!-- <groupId>cn.afterturn</groupId>-->
<!-- <artifactId>easypoi-spring-boot-starter</artifactId>-->
<!-- <version>4.4.0</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.javassist</groupId>-->
<!-- <artifactId>javassist</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
依赖有冲突示意图
3、代码(注意:我下面有时说的字段、有时说的属性,是一个东西)
(1)、动态创建类的工具类
/*** 动态生成 含有注解 @Excel 的实体工具类*/
public class CreateClassUtils {// class 所在的堆名public static final String CLASS_NAME_PREFIX = "com.example.test.pojo.CreateBeanDto@";// easyPOI 注解的类包名public static final String ANNOTATION_PACKAGE_NAME = "cn.afterturn.easypoi.excel.annotation.Excel";// 目前只能使用 字符串public static final String STRING_PACKAGE_NAME = "java.lang.String";/*** 动态生成类*/public static Class<?> generatePrototypeClass(List<BeanDto> list) throws NotFoundException, CannotCompileException, IOException {// 类目拼接String className = CLASS_NAME_PREFIX + UUID.randomUUID().toString();// 类池ClassPool pool = ClassPool.getDefault();// 往池子里面新增一个 类 赋名字CtClass clazz = pool.makeClass(className);// 获取类文件ClassFile ccFile = clazz.getClassFile();// 获取文件常量池ConstPool constpool = ccFile.getConstPool();// 添加字段addExpressField(pool, clazz, constpool, list);return clazz.toClass();}private static void addExpressField(ClassPool pool, CtClass clazz, ConstPool constpool, List<BeanDto> list) throws CannotCompileException, NotFoundException {// 将数据库查出动态附上 属性for (BeanDto bean : list) {// 属性需要小写 toLowerCase()addFieldAndAnnotation(pool, clazz, constpool, bean.getAnnotationName(), bean.getProperty().toLowerCase());}}/*** 添加字段和注解* 目前只支持字符串 其实基本上也够了 可以拿到字符串转成其他的类型* 如果后续我又兴趣会更新其他类型的*/private static void addFieldAndAnnotation(ClassPool pool, CtClass clazz, ConstPool constpool, String titleName, String fieldName) throws NotFoundException, CannotCompileException {// 生成fieldCtField field = new CtField(pool.get(STRING_PACKAGE_NAME), fieldName, clazz);field.setModifiers(Modifier.PRIVATE);// 添加 easyPoi 的注解AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);Annotation annotation = new Annotation(ANNOTATION_PACKAGE_NAME, constpool);// 添加注解名 name 的含义就是指 表头annotation.addMemberValue("name", new StringMemberValue(titleName, constpool));// 添加宽度annotation.addMemberValue("width", new DoubleMemberValue(30.0, constpool));fieldAttr.addAnnotation(annotation);field.getFieldInfo().addAttribute(fieldAttr);// 生成get/set方法clazz.addMethod(CtNewMethod.getter("get" + upperFirstLatter(fieldName), field));clazz.addMethod(CtNewMethod.setter("set" + upperFirstLatter(fieldName), field));// 类 添加属性clazz.addField(field);}/*** 拼接 get/set 方法*/public static String upperFirstLatter(String letter) {return letter.substring(0, 1).toUpperCase() + letter.substring(1);}/*** 需要动态生成类的参数*/@Datapublic static class BeanDto {/*** 属性名*/private String property;/*** 注解名*/private String annotationName;}
}
(2)、测试类
@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {/*** 导出实体*/@Datapublic static class ImportExcelDto {/*** 注解名*/private String[] annotationName;/*** 属性*/private String[] property;/*** 文件*/private MultipartFile file;}/*** 导入实体*/@Datapublic static class ExportExcelDto {/*** 注解名*/private String[] annotationName;/*** 属性*/private String[] property;}/*** 导出* 参数示意:* {* "property":["code","name","phone"],* "annotationName":["编码","名字","电话"]* }*/@PostMapping("/exportExcel")public void exportExcel(@RequestBody ExportExcelDto dto, HttpServletResponse response) throws Exception {try {exportExcelService(dto, response);} catch (Exception e) {log.error("exportExcel 报错", e);}}/*** 导入* 参数示意:// 表头字段String[] annotationName = new String[]{"名字", "电话"};// 属性字段String[] property = new String[]{"name", "phone"};*/@PostMapping("/importExcel")public Results importExcel(ImportExcelDto dto) throws Exception {try {importExcelService(dto);return Results.success();} catch (Exception e) {log.error("importExcel 报错", e);return Results.failed(e.getMessage());}}public void exportExcelService(ExportExcelDto dto, HttpServletResponse response) throws Exception {String[] name = dto.getAnnotationName();String[] property = dto.getProperty();// 模拟数据String datas = "[\n" +" {\n" +" \"code\":\"111\",\n" +" \"name\":\"222\",\n" +" \"phone\":\"333\"\n" +" },\n" +" {\n" +" \"code\":\"444\",\n" +" \"name\":\"555\",\n" +" \"phone\":\"666\"\n" +" },\n" +" {\n" +" \"code\":\"777\",\n" +" \"name\":\"888\",\n" +" \"phone\":\"999\"\n" +" }\n" +"]";if (name.length != property.length) {throw new Exception("入参错误");}// 遍历成我们需要的 值List<CreateClassUtils.BeanDto> beanDtos = new ArrayList<>();for (int i = 0; i < name.length; i++) {CreateClassUtils.BeanDto beanDto = new CreateClassUtils.BeanDto();beanDto.setAnnotationName(name[i]);beanDto.setProperty(property[i]);beanDtos.add(beanDto);}JSONArray data = JSONArray.parseArray(datas);// 动态生成excelClass<?> salaryArchivesClass = CreateClassUtils.generatePrototypeClass(beanDtos);//列名List<ExcelExportEntity> headList = new ArrayList<>();// 遍历表头for (CreateClassUtils.BeanDto beanDto : beanDtos) {String annotationName = beanDto.getAnnotationName();String key = beanDto.getProperty();// 这个 annotationName 就是excel的表头 key与导出的属性值对应// 也可以使用 salaryArchivesClass 中 注解 Excel 的 name 值ExcelExportEntity exportEntity = new ExcelExportEntity(annotationName, key);// 反射获取属性(字段)Field field = salaryArchivesClass.getDeclaredField(key);// 获取动态生成的 Excel 注解的 width 值if (field.isAnnotationPresent(Excel.class)) {Excel annotation = field.getAnnotation(Excel.class);double width = annotation.width();exportEntity.setWidth(width);}headList.add(exportEntity);}// 导出EasyPoiUtil.exportExcel(null, "666", "666.xlsx", headList, data, response);}/*** 导入服务*/public void importExcelService(ImportExcelDto dto) throws Exception {String[] annotationName = dto.getAnnotationName();String[] property = dto.getProperty();if (annotationName.length != property.length) {throw new Exception("入参错误");}List<CreateClassUtils.BeanDto> beanDtos = new ArrayList<>();// 把参数往 塞入 BeanDtofor (int i = 0; i < annotationName.length; i++) {CreateClassUtils.BeanDto beanDto = new CreateClassUtils.BeanDto();beanDto.setAnnotationName(annotationName[i]);beanDto.setProperty(property[i]);beanDtos.add(beanDto);}// 创建类Class<?> salaryArchivesClass = CreateClassUtils.generatePrototypeClass(beanDtos);// 新建实例Object o = salaryArchivesClass.newInstance();// 获取 CreateBeanDto 中的 get 方法Method get = salaryArchivesClass.getDeclaredMethod("get" + CreateClassUtils.upperFirstLatter(property[0].toLowerCase()));Object invoke = get.invoke(o);log.info("Object = {}", o);log.info("get = {}", invoke);List<?> objects = EasyPoiUtil.importExcel(dto.getFile(), 0, 1, salaryArchivesClass);String jsonString = JSON.toJSONString(objects);log.info("jsonString = {}", jsonString);}
}
(3)、easyPOI工具类
/*** @author ZHAOPINGAN* @Title: EasyPoiUtil* @ProjectName* @Description:*/
@Slf4j
public class EasyPoiUtil {/*** Excel大数据导入** @param file 文件信息* @param titleRows 标题占几行,从哪行开始读取 ,默认 0* @param headerRows header占几行 ,默认 1* @param pojoClass 对象Class*/public static <T> List<T> importBigExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws Exception {if (file == null) {throw new Exception("未找到上传的文件!");}ImportParams params = new ImportParams();params.setTitleRows(titleRows);params.setHeadRows(headerRows);params.setNeedVerify(true); // 开启校验规则List<T> list = new ArrayList<>();try {log.info("正在读取文件: " + file.getOriginalFilename() + ",开始导入数据。");ExcelImportUtil.importExcelBySax(file.getInputStream(), pojoClass, params, new IReadHandler<T>() {@Overridepublic void handler(T o) {list.add(o);}@Overridepublic void doAfterAll() {}});} catch (Exception e) {log.error("导入错误",e);} finally {file.getInputStream().close();}return list;}/*** Excel 普通根据本地路径导入* @param filePath 本地文件路径* @param titleRows 标题占几行,从哪行开始读取 ,默认 0* @param headerRows header占几行 ,默认 1* @param pojoClass 对象Class*/public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {if (StringUtils.isBlank(filePath)) {return null;}ImportParams params = new ImportParams();params.setTitleRows(titleRows);params.setHeadRows(headerRows);List<T> list = null;try {list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);} catch (NoSuchElementException e) {throw new ExcelImportException("模板不能为空");} catch (Exception e) {e.printStackTrace();throw new ExcelImportException(e.getMessage());}return list;}/*** Excel 普通导入* @param file 文件信息* @param titleRows 标题占几行,从哪行开始读取 ,默认 0* @param headerRows header占几行 ,默认 1* @param pojoClass 对象Class*/public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {if (file == null) {return null;}ImportParams params = new ImportParams();params.setTitleRows(titleRows);//标题占几行,从哪行开始读取params.setHeadRows(headerRows);//header占几行params.setSheetNum(1);List<T> list = null;try {list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);} catch (NoSuchElementException e) {throw new ExcelImportException("excel文件不能为空");} catch (Exception e) {throw new ExcelImportException(e.getMessage());}return list;}/*** 导出excel** @param list 数据list* @param title 标题* @param sheetName sheet名称* @param pojoClass 实体的class* @param fileName 文件名称* @param isCreateHeader 是否创建头* @param response 响应*/public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) {ExportParams exportParams = new ExportParams(title, sheetName);exportParams.setCreateHeadRows(isCreateHeader);defaultExport(list, pojoClass, fileName, response, exportParams);}/*** 导出excel** @param list 数据list* @param title 标题* @param sheetName sheet名称* @param pojoClass 实体class* @param fileName 文件名* @param response 响应*/public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));}/*** 导出excel2*/public static void exportExcel(String title, String sheetName, String fileName, List<ExcelExportEntity> entityList, List<?> list, HttpServletResponse response) {defaultExport(list, entityList, fileName, response, new ExportParams(title, sheetName));}/*** 导出excel** @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data* @param fileName 标题* @param response 响应*/public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {defaultExport(list, fileName, response);}/*** 返回字节码** @param list 数据list* @param title 标题* @param sheetName sheet名称* @param pojoClass 实体class*/public static ByteArrayOutputStream exportExcelBackByte(List<?> list, String title, String sheetName, Class<?> pojoClass) throws IOException {Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(title, sheetName), pojoClass, list);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();workbook.write(byteArrayOutputStream);return byteArrayOutputStream;}/*** Excel大数据导出** @param list 数据List* @param pojoClass 对象Class*/public static void exportBigExcelByte(Collection<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response) throws Exception {Workbook workbook;// 设置导出单sheet页最大一百万行数据ExportParams exportParams = new ExportParams();exportParams.setMaxNum(1000000);exportParams.setType(ExcelType.XSSF);workbook = ExcelExportUtil.exportBigExcel(exportParams, pojoClass, (queryParams, num) -> {// 只导出一次,第二次返回null终止循环if (((int) queryParams) == num) {return null;}log.info("正在进行大数据量导出,条数: " + list.size());return Arrays.asList(list.toArray());}, 2);OutputStream outputStream = null;try {response.setCharacterEncoding("UTF-8");response.setContentType("application/vnd.ms-excel");response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")+ ";" + "filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8"));outputStream = response.getOutputStream();workbook.write(outputStream);//保存Excel文件if (outputStream != null) {outputStream.close();//关闭文件流}} catch (Exception e) {log.info("execel流输出时错误,错误详情:{}", e.getMessage());throw new Exception("execel流输出时错误");}}/*** 导出多个excel** @param workbooks 多个excel文件 通过ExcelExportUtil.exportExcel往workbooks内放入excel* @param fileNames 文件名 每个excel文件的名称顺序必须一致且名称请务必保证不重复* @param fileName 压缩包文件名* @param response 标题*/public static void exportExcels(List<Workbook> workbooks, List<String> fileNames, String fileName, HttpServletResponse response) {try {
// response.setHeader("Content-Disposition",
// "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".zip");response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")+ ";"+ "filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8") + ".zip");OutputStream toClient = new BufferedOutputStream(response.getOutputStream());ZipOutputStream zipOut = new ZipOutputStream(toClient);for (int i = 0; i < workbooks.size(); i++) {ZipEntry entry = new ZipEntry(fileNames.get(i) + ".xls");zipOut.putNextEntry(entry);workbooks.get(i).write(zipOut);}zipOut.flush();zipOut.close();} catch (IOException e) {throw new ExcelExportException(e.getMessage());}}/*** 导出excel** @param list 数据list* @param pojoClass 实体class* @param fileName 文件名* @param response 响应*/private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) {Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);downLoadExcel(fileName, response, workbook);}/*** 导出excel2*/private static void defaultExport(List<?> list, List<ExcelExportEntity> entityList, String fileName, HttpServletResponse response, ExportParams exportParams) {Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list);downLoadExcel(fileName, response, workbook);}private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {try {response.setCharacterEncoding("UTF-8");response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")+ ";" + "filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8"));response.setContentType("application/vnd.ms-excel;charset=UTF-8");response.setHeader(HttpHeaders.PRAGMA, "no-cache");//关闭缓存response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");//关闭缓存workbook.write(response.getOutputStream());workbook.close();} catch (IOException e) {throw new ExcelImportException(e.getMessage());}}private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);downLoadExcel(fileName, response, workbook);}
}
(4)、json返回类(可以改成lombok的,我懒得改了)
/*** 用于返回* @param <T>*/
@ApiModel("统一返回类")
public class Results<T> {public static final String ERROR = "500";public static final String SUCCESS = "200";/*** 返回码*/@ApiModelProperty("返回码,正确码为:200")private String resCode ;/*** 返回消息*/@ApiModelProperty("返回消息")private String msg ;/*** 返回实体*/@ApiModelProperty("返回实体")private T obj;public static <T> Results<T> success(){return success(SUCCESS,"成功",null);}public static <T> Results<T> success(String msg){return success(SUCCESS,msg,null);}public static <T> Results<T> success(T obj){return success(SUCCESS,"成功",obj);}public static <T> Results<T> success(String msg,T obj){return success(SUCCESS,msg,obj);}public static <T> Results<T> success(String resCode,String msg,T obj){Results<T> result = new Results<T>();result.setResCode(resCode);result.setMsg(msg);result.setObj(obj);return result;}public static <T> Results<T> failed() {return failed(ERROR,"失败",null);}public static <T> Results<T> failed(String msg) {return failed(ERROR,msg,null);}public static <T> Results<T> failed(String msg,T obj) {return failed(ERROR,msg,obj);}public static <T> Results<T> failed(String resCode,String msg) {return failed(resCode,msg,null);}public static <T> Results<T> failed(Integer resCode,String msg) {return failed(String.valueOf(resCode),msg);}public static <T> Results<T> failed(String resCode,String msg,T obj) {Results<T> result = new Results<T>();result.setResCode(resCode);result.setMsg(msg);result.setObj(obj);return result;}public String getResCode() {return resCode;}public void setResCode(String resCode) {this.resCode = resCode;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getObj() {return obj;}public void setObj(T obj) {this.obj = obj;}@Overridepublic String toString() {return "Results{" +"resCode='" + resCode + '\'' +", msg='" + msg + '\'' +", obj=" + obj +'}';}
}
4、测试
(1)、导出
(2)、导入(使用导出的666.xlsx,就只用名字和电话两个字段来展示)
我的其他关于excel的文章(里面的工具类都有些许不同,我根据实际情况都有所删减和添加)
JAVA操作Excel(POI、easyPOI、easyExcel)
https://blog.csdn.net/qq_57581439/article/details/131206886