Grails中自动生成与原Domain类字段一致的历史Domain类咨询
当然可以自动生成对应的历史Domain类!手动创建重复的类不仅麻烦,还容易因为字段变更导致历史类和主类不一致,这里有几个实用的方案帮你解决这个问题:
方案1:用代码生成器快速生成
代码生成器是最直接的方式,比如用JavaPoet或者FreeMarker这类工具,写个简单的脚本就能批量生成历史类。举个JavaPoet的例子:
// 假设主类是Unit,我们来生成UnitHistory public class HistoryClassGenerator { public static void generateHistoryClass(Class<?> mainClass) throws IOException { // 获取主类的所有字段 Field[] fields = mainClass.getDeclaredFields(); // 构建历史类的JavaFile TypeSpec.Builder classBuilder = TypeSpec.classBuilder(mainClass.getSimpleName() + "History") .addModifiers(Modifier.PUBLIC); // 添加主类的所有字段 for (Field field : fields) { classBuilder.addField(FieldSpec.builder(field.getType(), field.getName()) .addModifiers(Modifier.PRIVATE) .build()); // 自动生成getter和setter classBuilder.addMethod(MethodSpec.methodBuilder("get" + capitalize(field.getName())) .addModifiers(Modifier.PUBLIC) .returns(field.getType()) .addStatement("return $N", field.getName()) .build()); classBuilder.addMethod(MethodSpec.methodBuilder("set" + capitalize(field.getName())) .addModifiers(Modifier.PUBLIC) .addParameter(field.getType(), field.getName()) .addStatement("this.$N = $N", field.getName(), field.getName()) .build()); } // 给历史类添加必要的额外字段:操作时间、操作人、变更类型 classBuilder.addField(FieldSpec.builder(Date.class, "operateTime") .addModifiers(Modifier.PRIVATE) .build()); classBuilder.addField(FieldSpec.builder(String.class, "operator") .addModifiers(Modifier.PRIVATE) .build()); classBuilder.addField(FieldSpec.builder(String.class, "changeType") .addModifiers(Modifier.PRIVATE) .build()); // 别忘了给这些字段也生成getter和setter // 生成Java文件到指定目录 JavaFile javaFile = JavaFile.builder(mainClass.getPackageName(), classBuilder.build()) .build(); javaFile.writeTo(new File("src/main/java")); } private static String capitalize(String name) { return name.substring(0, 1).toUpperCase() + name.substring(1); } }
你可以在项目启动时或者通过IDE插件触发这个生成逻辑,只要主类字段变更,重新运行生成就能同步更新历史类。
方案2:自定义注解+编译期注解处理器
如果想让生成过程更自动化,比如编译主类时自动生成历史类,可以用注解处理器。步骤大概是:
- 定义一个自定义注解,标记需要生成历史类的主Domain:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface GenerateHistory { // 可以添加参数,比如历史类的后缀名,默认是"History" String suffix() default "History"; }
- 编写注解处理器,在编译时扫描带有
@GenerateHistory的类,自动生成对应的历史类:
@SupportedAnnotationTypes("com.yourpackage.GenerateHistory") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class HistoryAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement annotation : annotations) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation); for (Element element : elements) { if (element.getKind() == ElementKind.CLASS) { TypeElement classElement = (TypeElement) element; // 这里可以用JavaPoet或者直接拼接字符串生成历史类的代码 // 逻辑和方案1类似,获取主类的字段,生成历史类 generateHistoryClass(classElement); } } } return true; } private void generateHistoryClass(TypeElement classElement) { // 实现代码生成逻辑,比如用JavaPoet } }
配置好注解处理器后,只要你给主类加上@GenerateHistory,编译项目时就会自动生成对应的历史类,完全不用手动操作。
方案3:借助ORM框架的代码生成能力
如果你用的是MyBatis-Plus或者Spring Data JPA这类ORM框架,它们自带的代码生成器可以自定义模板,让生成主Domain的同时自动生成历史Domain。比如MyBatis-Plus的代码生成器,你可以修改实体类的模板,添加生成历史类的逻辑,或者直接复制一份模板专门用于生成历史类,配置好生成规则就行。
额外注意点
- 历史类通常需要额外的字段(比如操作时间、操作人、变更类型),生成时记得自动添加这些字段,不用手动写。
- 要保证主类字段变更时,历史类能同步更新,所以最好用编译期生成或者启动时生成的方式,避免手动维护不一致。
- 如果用注解处理器,记得在项目的pom.xml或者build.gradle里配置好处理器的依赖,让编译时能识别到。
内容的提问来源于stack exchange,提问作者Prakash
相关产品推荐
相关产品推荐

