java动态导入导出excel,javassist动态创建类
Javassist 使 Java 字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库。它使 Java 程序可以在运行时定义新类,并在 JVM 加载它时修改类文件。与其他类似的字节码编辑器不同,Javassist 提供了两个级别的 API:源级别和字节代码级别。如果用户使用源代码级 API,则他们可以在不了解 Java 字节码规范的情况下编辑类文件。整个 API 仅使用 Java 语言
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 {
// 生成field
CtField 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);
}
/**
* 需要动态生成类的参数
*/
@Data
public static class BeanDto {
/**
* 属性名
*/
private String property;
/**
* 注解名
*/
private String annotationName;
}
}
(2)、测试类
@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {
/**
* 导出实体
*/
@Data
public static class ImportExcelDto {
/**
* 注解名
*/
private String[] annotationName;
/**
* 属性
*/
private String[] property;
/**
* 文件
*/
private MultipartFile file;
}
/**
* 导入实体
*/
@Data
public 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);
// 动态生成excel
Class<?> 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<>();
// 把参数往 塞入 BeanDto
for (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>() {
@Override
public void handler(T o) {
list.add(o);
}
@Override
public 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;
}
@Override
public 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
更多推荐
所有评论(0)