利用Javassist和EasyPoi动态生成指定表头的Excel

这篇博客介绍了如何利用Java的CGLIB库动态生成带有@Data和@ExcelTarget注解的类,用于方便地进行Excel数据导出。通过创建并添加字段、注解、getter/setter方法,最后使用EasyPoi库实现Excel文件的生成。该技术在数据处理和自动化报表生成中非常实用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// fields 格式为 {"类的属性名":"Excel表头字段名"}
public Class<?> addAnnotation(Map<String, String> fields, String className) throws CannotCompileException, NotFoundException {
		// 获取导出类所有的属性名
        List<String> fieldList = new ArrayList<>(fields.keySet());
        // 单例模式初始化 ClassPool
        ClassPool pool = ClassPool.getDefault();
        // 可以取一个没有进行实例化的类。这里我们会从头生成一个符合导入要求的新类
        CtClass ctClass = pool.makeClass(className);

		// 给新生成的类添加 Serializable 接口
        ctClass.addInterface(pool.get(Serializable.class.getName()));

        ClassFile classFile = ctClass.getClassFile();
        ConstPool constpool = classFile.getConstPool();
		
		// 给新生成的类添加 @Data 注解和 @ExcelTarget(value = "新生成类的类名") 注解
		// 这个地方的 @Data 其实并没有起到作用(待研究)
        AnnotationsAttribute classAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(Data.class.getCanonicalName(), constpool);

        AnnotationsAttribute classAttr1 = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annotation1 = new Annotation(ExcelTarget.class.getCanonicalName(), constpool);
        annotation1.addMemberValue("value", new StringMemberValue(className.substring(className.lastIndexOf('.') + 1), constpool));

        Annotation[] annotations = new Annotation[]{annotation, annotation1};
        classAttr.setAnnotations(annotations);
        ctClass.getClassFile().addAttribute(classAttr);

        // 给新生成的类添加字段
        for (int i = 0; i < fieldList.size(); i++) {
            // 增加字段
            CtField field = new CtField(pool.get(String.class.getCanonicalName()),fieldList.get(i),ctClass);
            field.setModifiers(Modifier.PRIVATE);
            FieldInfo fieldInfo = field.getFieldInfo();
            ctClass.addField(field);

            //创建要添加的注解
            Annotation fieldAnnotation = new Annotation(Excel.class.getCanonicalName(), constpool);
            //设置注解中的属性和值,目标格式为 @Excel(name = "Excel表头字段名")
            fieldAnnotation.addMemberValue("name", new StringMemberValue(fields.get(fieldList.get(i)), constpool));

            //添加getter setter方法
            ctClass.addMethod(CtNewMethod.setter("set" + UpFirst(fieldList.get(i)), field));
            ctClass.addMethod(CtNewMethod.getter("get" + UpFirst(fieldList.get(i)), field));
            AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
            annotationsAttribute.addAnnotation(fieldAnnotation);
            field.getFieldInfo().addAttribute(annotationsAttribute);
        }
		// 这里重新 new 一个 ClassLoader, 是为了防止一个类加载器加载两个相同的类导致报错
        Class<?> res = ctClass.toClass(new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                return super.loadClass(name);
            }
        });
        // 如果CtClass通过writeFile(),toClass(),toBytecode()转换了类文件,javassist冻结了CtClass对象。以后是不允许修改这个 CtClass对象。这是为了警告开发人员当他们试图修改一个类文件时,已经被JVM载入的类不允许被重新载入。这里我们为了可以多次修改类文件,所以进行手动解冻
        // ctClass.writeFile("E:/workspace/annotation"); 可以将新生成的类的 class 文件持久化在本地。
        if (ctClass.isFrozen()){
            ctClass.defrost();
        }
        return res;
    }
    
    
    // 将 set/get 方法的属性名首字母大写
    private static String UpFirst(String str){
        return str.substring(0,1).toUpperCase() + str.substring(1);
    }

我们将 CtClass.writeFile 保存的 class 文件进行反编译,可以看到如下代码:
在这里插入图片描述

调用我们的 addAnnotation 方法获得新生成的类,并进行 Excel 导出

public class Main{
	public static void main(String[] args){
		Map<String, String> fields = new HashMap();
		fields.put("name","姓名");
		fields.put("age", "年龄");
		Class<?> annotatedClass = addAnnotation(fields, xxx.class.getName());
        List<Object> list = new ArrayList<>();
        // xxxService.list() 从数据库查询完整数据
        for (Xxx xxx : xxxService.list()) {
            Object target = annotatedClass.newInstance();
            // 将其中部分数据的值复制到我们新生成的类上面
            BeanUtil.copyProperties(xxx, target);
            list.add(target);
        }
		// EasyPoi 导出
        List<EasyPoiUtil.SheetConfig> configList = new ArrayList<>();
        EasyPoiUtil.SheetConfig sheetConfig = new EasyPoiUtil.SheetConfig();
        sheetConfig.setName("Excel表名");
        sheetConfig.setData(list);
        configList.add(sheetConfig);

        String fileName = "Excel文件名.xlsx";
        try {
            EasyPoiUtil.exportExcel(configList, fileName, annotatedClass, true, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}
### 动态生成表头的方法 在 EasyExcel动态生成表头可以通过以下方法实现,特别是在写入数据时结合动态生成的实体类 `entity` 使用。 #### 1. 使用动态字段映射 EasyExcel 支持通过注解 `@ExcelProperty` 映射字段与 Excel 表头。如果需要动态生成表头,可以利用反射机制动态设置字段名称,并在运行时生成对应的实体类[^1]。 ```java import com.alibaba.excel.annotation.ExcelProperty; public class DynamicEntity { @ExcelProperty("动态字段1") private String field1; @ExcelProperty("动态字段2") private String field2; // Getter Setter 方法 public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } } ``` #### 2. 动态生成表头 如果表头是完全动态的,可以在写入时通过 `Head` 类手动指定表头信息。这种方式不需要依赖实体类,直接定义表头内容并绑定到数据列表中[^2]。 ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.metadata.BaseRowModel; import com.alibaba.excel.metadata.Head; import java.util.ArrayList; import java.util.List; public class DynamicHeadExample { public static void main(String[] args) { // 定义动态表头 List<Head> headList = new ArrayList<>(); headList.add(new Head("动态表头1")); headList.add(new Head("动态表头2")); // 准备数据 List<List<String>> data = new ArrayList<>(); List<String> row1 = new ArrayList<>(); row1.add("值1"); row1.add("值2"); data.add(row1); // 写入文件 String filename = "dynamic_head.xlsx"; WriteSheet writeSheet = new WriteSheet(); writeSheet.setSheetName("动态表头示例"); EasyExcel.write(filename).head(headList).sheet("Sheet1").doWrite(data); } } ``` #### 3. 结合动态实体类动态表头 如果需要同时使用动态生成的实体类动态表头,可以通过反射机制动态生成实体类,并在运行时为字段添加 `@ExcelProperty` 注解。以下是具体实现步骤: - **动态生成实体类**:使用 Java 的字节码生成工具(如 Javassist 或 ASM)创建动态实体类。 - **动态设置表头**:通过反射机制为动态生成的实体类字段添加 `@ExcelProperty` 注解。 ```java import com.alibaba.excel.annotation.ExcelProperty; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import java.lang.reflect.Field; public class DynamicEntityGenerator { public static Class<?> generateDynamicEntity(List<String> headers) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("DynamicGeneratedEntity"); for (int i = 0; i < headers.size(); i++) { CtField ctField = new CtField(pool.get("java.lang.String"), "field" + i, ctClass); ctField.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField); // 添加 getter setter 方法 ctClass.addMethod(CtNewMethod.setter("setField" + i, ctField)); ctClass.addMethod(CtNewMethod.getter("getField" + i, ctField)); // 动态添加 @ExcelProperty 注解 Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(ctField.getField(), ctField.getField().getModifiers() & ~Modifier.FINAL); ctField.getField().setAnnotation(new ExcelProperty(headers.get(i))); } return ctClass.toClass(); } } ``` #### 4. 示例代码 以下是一个完整的示例,展示如何结合动态生成的实体类动态表头进行写入操作。 ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.metadata.WriteSheet; import java.util.ArrayList; import java.util.List; public class DynamicWriteExample { public static void main(String[] args) throws Exception { // 定义动态表头 List<String> headers = new ArrayList<>(); headers.add("动态表头1"); headers.add("动态表头2"); // 动态生成实体类 Class<?> dynamicEntityClass = DynamicEntityGenerator.generateDynamicEntity(headers); // 准备数据 List<Object> data = new ArrayList<>(); Object entity = dynamicEntityClass.getDeclaredConstructor().newInstance(); dynamicEntityClass.getMethod("setField0", String.class).invoke(entity, "值1"); dynamicEntityClass.getMethod("setField1", String.class).invoke(entity, "值2"); data.add(entity); // 写入文件 String filename = "dynamic_entity_write.xlsx"; WriteSheet writeSheet = new WriteSheet(); writeSheet.setSheetName("动态实体类示例"); EasyExcel.write(filename, dynamicEntityClass).sheet("Sheet1").doWrite(data); } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值