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
相关产品推荐
相关产品推荐

