You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 7动态添加/更新Annotation失败,寻求技术帮助

在Java 7中动态添加字段注解失败的问题分析与解决

你在Java 7里尝试给字段动态添加ColumnTransformer注解却失败的问题,我帮你梳理下两种实现的核心问题,再给出可行的修复方案:

一、Javassist实现的问题与修正

你的Javassist代码存在三个关键问题:

  • 重复复用同一个注解属性对象:循环中你一直往同一个attr里加注解,但每个字段需要独立的注解属性集合,否则会导致注解混乱甚至覆盖。
  • 错误的属性添加方式:直接调用atts.add(attr)不可靠,因为getAttributes()返回的可能是不可修改的列表视图,正确做法是用propField.getFieldInfo().addAttribute(attr)来添加注解属性。
  • 已加载类的字节码无法直接生效:如果Partner类已经被JVM加载,单纯修改字节码文件不会影响内存中的类实例,这时候要么在类加载前修改字节码,要么用自定义类加载器重新加载修改后的类。

修正后的Javassist代码:

public void javaassist(String className, List<String> props) throws Exception{
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.getCtClass(className);
    
    // 如果类已被加载冻结,先解除冻结(仅测试环境使用,生产需谨慎)
    if (cc.isFrozen()) {
        cc.defrost();
    }

    for(String propName: props) {
        // 为每个字段创建独立的AnnotationsAttribute
        ClassFile ccFile = cc.getClassFile();
        ConstPool constpool = ccFile.getConstPool();
        AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        
        javassist.bytecode.annotation.Annotation annot = new javassist.bytecode.annotation.Annotation(
            "org.hibernate.annotations.ColumnTransformer", constpool);
        annot.addMemberValue("read", new StringMemberValue("aes_decrypt("+propName+", 'mysecret')", constpool));
        annot.addMemberValue("write", new StringMemberValue("aes_encrypt(?, 'mysecret')", constpool));
        attr.addAnnotation(annot);
        
        CtField propField = cc.getDeclaredField(propName);
        // 正确将注解属性添加到字段
        propField.getFieldInfo().addAttribute(attr);
    }
    
    // 若需立即使用修改后的类,需通过自定义类加载器加载(示例)
    // Class<?> modifiedClass = cc.toClass(new CustomClassLoader(), null);
}

二、反射实现的核心错误

你的反射代码完全找错了注解的存储位置:

  • Class.class.getDeclaredField("annotations")对应的是类级别的注解集合,而你要给字段加注解,应该操作Field对象的注解集合,而非Class对象的。
  • 即使找到Field的注解字段,Java 7中Field的getAnnotations()方法会缓存结果,直接修改注解集合后需要清除缓存才能看到新注解。

修正后的反射代码:

@SuppressWarnings({"unchecked", "rawtypes"})
private void withNew(Class clazz, List<String> props) throws Exception {
    String key = "mysecret";
    for(String prop: props) {
        Field field = clazz.getDeclaredField(prop);
        
        // 获取Field对象中存储注解的declaredAnnotations字段
        Field declaredAnnotationsField = Field.class.getDeclaredField("declaredAnnotations");
        declaredAnnotationsField.setAccessible(true);
        Map<Class<? extends Annotation>, Annotation> annotations = 
            (Map<Class<? extends Annotation>, Annotation>) declaredAnnotationsField.get(field);
        
        // 构建自定义ColumnTransformer注解实例
        ColumnTransformer ctAnnotation = buildColTransfAnnotation(prop, key);
        annotations.put(ColumnTransformer.class, ctAnnotation);
        
        // 清除Field的注解缓存,确保getAnnotations()返回最新结果
        Field annotationsCacheField = Field.class.getDeclaredField("annotations");
        annotationsCacheField.setAccessible(true);
        annotationsCacheField.set(field, null);
    }
}

private ColumnTransformer buildColTransfAnnotation(final String propName, final String key) {
    return new ColumnTransformer() {
        @Override
        public Class<? extends Annotation> annotationType() {
            return ColumnTransformer.class;
        }
        @Override
        public String write() {
            return "aes_encrypt(?, '"+key+"')";
        }
        @Override
        public String read() {
            return "aes_decrypt("+propName+", '"+key+"')";
        }
        @Override
        public String forColumn() {
            return null;
        }
    };
}

三、验证时的注意事项

  • 若使用Javassist修改了已加载的类,必须用自定义类加载器加载修改后的类,再通过这个加载后的类对象获取字段,而不是直接用原来的Partner.class。
  • 反射修改后,调用field.getAnnotations()前一定要清除缓存,否则会返回修改前的旧注解集合。

内容的提问来源于stack exchange,提问作者hdk.pnchl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:32:32