对于新增类应用场景不常见,而修改现有类应用场景更多,比如常见的日志切面,权限切面。

修改现有.class文件

已有类新增方法

1、现有类Person

public class Person {private String name;public Person() {name = "init";}public void foo(){System.out.println("foo=" + name);}
}

2、对上面这个类进行修改:

public class UpdateBean {public static void main(String[] args) {/* 1、获取默认ClassPath 下的 ClassPool */ClassPool pool = ClassPool.getDefault();try {/* 2、获取需要修改的类 */CtClass ctClass = pool.get("com.ymqx.动态增加属性和注解.Person");/* 3、获取指定方法,修改其方法体*/CtMethod foo = ctClass.getDeclaredMethod("foo");foo.insertBefore("System.out.println(\"before=\" + name);");foo.insertAfter("System.out.println(\"after=\" + name);");//setBody()会覆盖重写原方法的内容//foo.setBody("System.out.println(\"set new body\");");/* 4、新增一个方法*/CtMethod ctMethod = new CtMethod(CtClass.voidType, "newFoo", new CtClass[]{}, ctClass);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("{System.out.println(\"i am new foo\");}");ctClass.addMethod(ctMethod);/* 5、生成字节码文件,方便查看创建出来的类的结果 */ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (Exception e) {e.printStackTrace();}}
}

注:setBody()会覆盖重写原方法的内容。

3、修改后的Person.classs:

public class Person {private String name = "init";public Person() {}public void foo() {System.out.println("before=" + this.name);System.out.println("foo=" + this.name);Object var2 = null;System.out.println("after=" + this.name);}public void newFoo() {System.out.println("i am new foo");}
}

4、调用:

public class GetBean {public static void main(String[] args) {//从classLoader中取出Person类的类对象ClassPool classPool = ClassPool.getDefault();Class<?> clazz = null;Object bean = null;try {//设置类路径classPool.appendClassPath("D:\\中能电力\\代码\\MyTest\\target\\classes\\");CtClass ctClass = classPool.get("com.ymqx.动态增加属性和注解.Person");/* 实例化对象,并调用方法*/Object obj = ctClass.toClass().newInstance();Method method1 = obj.getClass().getMethod("foo");method1.invoke(obj);Method method2 = obj.getClass().getMethod("newFoo");method2.invoke(obj);} catch (Exception e) {e.printStackTrace();}}
}输出:
before=init
foo=init
after=init
i am new foo

已有类新增属性注解

1、工具类JavasistUtils:

  • addAnnotationToField:新增指定属性的指定注解
  • removeAnnotationFromField:删除指定属性的指定注解
  • printAnno:反射方式,遍历对象所有注解值
  • queryAnnotation:Javasist方式,遍历指定属性下所有注解
  • getAnnotationsAttributeFromField:获取指定属性的注解集合
  • retransformClass:使用Byte Buddy重新转换类
public class JavasistUtils {/*** 新增指定属性的指定注解** @param clazz     类对象* @param fieldName 类属性* @param annotationClass 注解类对象* @param initAnnotation  注解属性*/public static void addAnnotationToField(Class<?> clazz, String fieldName, Class<?> annotationClass,BiConsumer<Annotation, ConstPool> initAnnotation) {ClassPool pool = ClassPool.getDefault();CtClass ctClass;try {ctClass = pool.getCtClass(clazz.getName());if (ctClass.isFrozen()) {ctClass.defrost();}CtField ctField = ctClass.getDeclaredField(fieldName);ConstPool constPool = ctClass.getClassFile().getConstPool();Annotation annotation = new Annotation(annotationClass.getName(), constPool);if (initAnnotation != null) {initAnnotation.accept(annotation, constPool);}AnnotationsAttribute attr = getAnnotationsAttributeFromField(ctField);if (attr == null) {attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);ctField.getFieldInfo().addAttribute(attr);}attr.addAnnotation(annotation);/* 生成字节码文件,方便查看创建出来的类的结果 */String path = System.getProperty("user.dir") + "\\target\\classes";ctClass.writeFile(path);ctClass.defrost();//retransformClass(clazz, ctClass.toBytecode());} catch (NotFoundException | CannotCompileException | IOException e) {e.printStackTrace();}}/*** 删除指定属性的指定注解** @param clazz     类对象* @param fieldName 类属性* @param annotationClass 注解类对象*/public static void removeAnnotationFromField(Class<?> clazz, String fieldName, Class<?> annotationClass) {ClassPool pool = ClassPool.getDefault();CtClass ctClass;try {ctClass = pool.getCtClass(clazz.getName());if (ctClass.isFrozen()) {ctClass.defrost();}CtField ctField = ctClass.getDeclaredField(fieldName);AnnotationsAttribute attr = getAnnotationsAttributeFromField(ctField);if (attr != null) {attr.removeAnnotation(annotationClass.getName());}retransformClass(clazz, ctClass.toBytecode());} catch (NotFoundException | IOException | CannotCompileException e) {e.printStackTrace();}}/*** 反射方式,遍历对象所有注解值** @param obj  对象*/public static void printAnno(Object obj) {// 遍历获取Field[] 集合Field[] declaredFields = obj.getClass().getDeclaredFields();for (Field f : declaredFields) {// 遍历获取Annotation[] 集合System.out.println(f.getName() + ":");java.lang.annotation.Annotation[] annotations = f.getDeclaredAnnotations();for (java.lang.annotation.Annotation anno : annotations) {System.out.println("\t" + anno);}}}/*** 遍历指定属性下所有注解** @param obj       对象* @param FieldName 类属性*/public static void queryAnnotation(Object obj, String FieldName) {//System.out.println("====开始遍历注解所有属性=====");try {ClassPool pool = ClassPool.getDefault();//获取实体类CtClass ct = pool.get(obj.getClass().getName());if (ct.isFrozen()) {ct.defrost();}//获取属性CtField cf = ct.getField(FieldName);//获取属性字段信息FieldInfo fieldInfo = cf.getFieldInfo();//获取属性字段的运行时可见注解AnnotationsAttribute attribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag);if (null != attribute) {//获取所有注解Annotation[] annotations = attribute.getAnnotations();int sum = 0;//遍历注解for (Annotation annotation : annotations) {System.out.println("注解" + (++sum) + ":" + annotation.toString());//如果没有属性名,就下一个循环if (annotation.getMemberNames() == null) {System.out.println("!无属性名跟属性值!");continue;}//获取注解的所有属性名、属性值for (String memberName : annotation.getMemberNames()) {/*System.out.println("获取到的注解的属性名:" + memberName);System.out.println("获取到的注解的属性值:" + annotation.getMemberValue(memberName));*/}}} else {System.out.println("未获取到属性字段的注解");}} catch (NotFoundException e) {System.out.println("此类不存在" + e);}}/*** 获取指定属性的注解集合** @param ctField 类属性*/private static AnnotationsAttribute getAnnotationsAttributeFromField(CtField ctField) {List<AttributeInfo> attrs = ctField.getFieldInfo().getAttributes();AnnotationsAttribute attr = null;if (attrs != null) {Optional<AttributeInfo> optional = attrs.stream().filter(AnnotationsAttribute.class::isInstance).findFirst();if (optional.isPresent()) {attr = (AnnotationsAttribute) optional.get();}}return attr;}/*** 使用Byte Buddy重新转换类** @param clazz    类对象* @param byteCode 类文件的二进制数组*/private static void retransformClass(Class<?> clazz, byte[] byteCode) {ClassFileTransformer cft = new ClassFileTransformer() {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) {return byteCode;}};Instrumentation instrumentation = ByteBuddyAgent.install();try {instrumentation.addTransformer(cft, true);instrumentation.retransformClasses(clazz);} catch (UnmodifiableClassException e) {e.printStackTrace();} finally {instrumentation.removeTransformer(cft);}}
}

2、测试类 UpdateBean:

public class UpdateBean {public static void main(String[] args) {Person person = new Person();System.out.println("toJSONString:"+ JSONObject.toJSONString(person));System.out.println("+++++增加注解前++++++");JavasistUtils.printAnno(person);JavasistUtils.queryAnnotation(person,"name");JavasistUtils.addAnnotationToField(person.getClass(), "name", JSONField.class, (annotation, constPool) -> {annotation.addMemberValue("serialize", new BooleanMemberValue(false, constPool));});System.out.println("+++++增加注解后++++++");JavasistUtils.printAnno(person);JavasistUtils.queryAnnotation(person,"name");System.out.println("toJSONString:"+JSONObject.toJSONString(person));Person person2 = new Person();System.out.println("+++++增加注解后++++++");JavasistUtils.printAnno(person2);JavasistUtils.queryAnnotation(person2,"name");System.out.println("toJSONString:"+ JSONObject.toJSONString(person2));}
}运行结果:
toJSONString:{"name":"init"}
+++++增加注解前++++++
name:
未获取到属性字段的注解
+++++增加注解后++++++
name:
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)
toJSONString:{"name":"init"}
+++++增加注解后++++++
name:
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)
toJSONString:{"name":"init"}

运行结果看出,反射方式遍历对象所有注解值没有改变。而通过 Javasist 方式获取类对象的注解改变了。
但问题是,通过 JSONObject.toJSONString() 方法打印对象结果还是 "name":"init" ,新增的注解 @JSONField(serialize = false) 并没有生效。o(╯□╰)o

3、修改后的Person.classs:

public class Person {@JSONField(serialize = false)public String name = "init";public Person() {}public void foo() {System.out.println("foo=" + this.name);}
}

运行结果发现,原先.class文件已经修改,但是通过反射的方式获取不到新增注解,通过 CtClass 可以获取到,但注解没有生效(字段name值打印出来)。

其实可以理解,因为当前运行环境读取的 Person 类是未修改前的,修改后的Person类就没有加载至当前线程的上下文类加载器中,所以 JSONObject.toJSONString() 打印还是原先值。

我们可以重新加载Person类:

public class GetBean {public static void main(String[] args) {ClassPool classPool = ClassPool.getDefault();Class<?> clazz = null;Object bean = null;try {//方式一:classPool.appendClassPath("D:\\中能电力\\代码\\MyTest\\target\\classes\\");CtClass ctClass = classPool.get("com.ymqx.动态增加属性和注解.Person");/* 实例化对象,并调用方法*/Object obj = ctClass.toClass().newInstance();System.out.println("+++++方式一++++++");System.out.println("toJSONString:"+ JSONObject.toJSONString(obj));JavasistUtils.printAnno(obj);JavasistUtils.queryAnnotation(obj,"name");//方式二:Person person = new Person();System.out.println("+++++方式二++++++");System.out.println("toJSONString:"+ JSONObject.toJSONString(person));JavasistUtils.printAnno(person);JavasistUtils.queryAnnotation(person,"name");} catch (Exception e) {e.printStackTrace();}}
}运行结果:
+++++方式一++++++
toJSONString:{}
name:@com.alibaba.fastjson.annotation.JSONField(format=, label=, jsonDirect=false, unwrapped=false, deserializeUsing=class java.lang.Void, parseFeatures=[], serialize=false, alternateNames=[], serialzeFeatures=[], name=, serializeUsing=class java.lang.Void, ordinal=0, deserialize=true)
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)
+++++方式二++++++
toJSONString:{}
name:@com.alibaba.fastjson.annotation.JSONField(format=, label=, jsonDirect=false, unwrapped=false, deserializeUsing=class java.lang.Void, parseFeatures=[], serialize=false, alternateNames=[], serialzeFeatures=[], name=, serializeUsing=class java.lang.Void, ordinal=0, deserialize=true)
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)

再次获取Person类就是修改后的了,注解也生效了(字段name值未打印)。

引申一

Javassist基本用法 中 CtClass 类有个方法 toClass 可以将CtClass加载至当前线程的上下文类加载器中,那修改下方法 addAnnotationToField

public static void addAnnotationToField(Class<?> clazz, String fieldName, Class<?> annotationClass,BiConsumer<Annotation, ConstPool> initAnnotation) {.../* 生成字节码文件,方便查看创建出来的类的结果 *//*String path = System.getProperty("user.dir") + "\\target\\classes";ctClass.writeFile(path);ctClass.defrost();*/ctClass.toClass();ctClass.defrost();} catch (NotFoundException | CannotCompileException e) {e.printStackTrace();}}运行结果:
toJSONString:{"name":"init"}
+++++增加注解前++++++
name:
未获取到属性字段的注解
javassist.CannotCompileException: by java.lang.ClassFormatError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "com/ymqx/动态增加属性和注解/Person"at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:271)at javassist.ClassPool.toClass(ClassPool.java:1240)at javassist.ClassPool.toClass(ClassPool.java:1098)at javassist.ClassPool.toClass(ClassPool.java:1056)at javassist.CtClass.toClass(CtClass.java:1298)at com.ymqx.动态增加属性和注解.JavasistUtils.addAnnotationToField(JavasistUtils.java:62)at com.ymqx.动态增加属性和注解.UpdateBean.main(UpdateBean.java:26)

报错:attempted duplicate class definition for name,试图重复定义同一个类。

因为Person person = new Person();已经将Person类加载到当前ClassLoader,而ctClass.toClass();调用了 ClassLoader 的 defineClass 方法实现的类加载,导致重复加载同一个类。

引申二

那有没有解决方案呢?可以使用Byte Buddy重新转换类。

public static void addAnnotationToField(Class<?> clazz, String fieldName, Class<?> annotationClass,BiConsumer<Annotation, ConstPool> initAnnotation) {.../* 生成字节码文件,方便查看创建出来的类的结果 *//*String path = System.getProperty("user.dir") + "\\target\\classes";ctClass.writeFile(path);ctClass.defrost();*/retransformClass(clazz, ctClass.toBytecode());} catch (NotFoundException | CannotCompileException | IOException e) {e.printStackTrace();}}运行结果:
toJSONString:{"name":"init"}
+++++增加注解前++++++
name:
未获取到属性字段的注解
+++++增加注解后++++++
name:@com.alibaba.fastjson.annotation.JSONField(format=, label=, jsonDirect=false, unwrapped=false, deserializeUsing=class java.lang.Void, parseFeatures=[], serialize=false, alternateNames=[], serialzeFeatures=[], name=, serializeUsing=class java.lang.Void, ordinal=0, deserialize=true)
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)
toJSONString:{"name":"init"}
+++++增加注解后++++++
name:@com.alibaba.fastjson.annotation.JSONField(format=, label=, jsonDirect=false, unwrapped=false, deserializeUsing=class java.lang.Void, parseFeatures=[], serialize=false, alternateNames=[], serialzeFeatures=[], name=, serializeUsing=class java.lang.Void, ordinal=0, deserialize=true)
注解1:@com.alibaba.fastjson.annotation.JSONField(serialize=false)
toJSONString:{"name":"init"}

结果可见,执行retransformClass方法后,不管是反射获取还是Javasist都能获取到修改后的注解。但是,,,注解还是没有生效(字段name值打印出来)。

不知道为什么?个人感觉可能读取的还是原先的Person类。

已有类新增字段

1、现有类TargetBean、注解TestAnno

public class TargetBean {private String val;public TargetBean() {}public TargetBean(String val) {this.val = val;}public String getVal() {return val;}public void setVal(String val) {this.val = val;}
}@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnno {String name();String type() default "1";
}

2、工具类JavasistUtils增加方法:

  • addFields:动态添加字段和注解
public class JavasistUtils {/*** 功能:动态添加字段和注解**/public static void addFields(Object obj, Map<String, Map<String, Map<String, String>>> properties, String writeFilePath) {//获取类池ClassPool pool = ClassPool.getDefault();try {//获取实体类CtClass ctClass = pool.get(obj.getClass().getName());//class File这个术语定义于虚拟机规范3.1,指的是字节码的byte数组,而不是文件系统中的class文件。ClassFile classFile = ctClass.getClassFile();//获取常量池ConstPool constPool = classFile.getConstPool();for (String fieldKey : properties.keySet()) {//System.out.println("fieldKey=" + fieldKey);CtField ctField = new CtField(pool.get(String.class.getCanonicalName()), fieldKey, ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);// 新增注解属性池AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);for (String annoKey : properties.get(fieldKey).keySet()) {//System.out.println("annoKey=" + annoKey);//创建要添加的注解Annotation anno = new Annotation(Class.forName(annoKey).getCanonicalName(), constPool);//设置注解中的属性和值properties.get(fieldKey).get(annoKey).forEach((k, v) -> {//System.out.println("k=" + k + ",v=" + v);anno.addMemberValue(k, new StringMemberValue(v, constPool));});//把这个注解放到一个AnnotationsAttribute对象里面annotationsAttribute.addAnnotation(anno);}//把这个对象放在要打上这个注解的字段/类上面ctField.getFieldInfo().addAttribute(annotationsAttribute);}//生成字节码文件,方便查看创建出来的类的结果if (writeFilePath != null) {ctClass.writeFile(writeFilePath);}//解决方案二:使用Byte Buddy字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类//retransformClass(obj.getClass(), ctClass.toBytecode());} catch (Exception e) {e.printStackTrace();}}
}

3、测试类 UpdateBean:

public class UpdateBean {public static void main(String[] args) {TargetBean bean = new TargetBean();JavasistUtils.printAnno(bean);/* 设置需要新增的字段和注解 */HashMap<String, String> annoValueMap1 = new HashMap<>();annoValueMap1.put("name","change1");annoValueMap1.put("type","1");HashMap<String, Map<String, String>> annoMap1 = new HashMap<>();annoMap1.put(TestAnno.class.getName(), annoValueMap1);HashMap<String, Map<String, Map<String, String>>> filedMap = new HashMap<>();filedMap.put("val2", annoMap1);String writeFilePath = System.getProperty("user.dir") + "\\target\\classes";/* 调用方法新增字段 */JavasistUtils.addFields(bean, filedMap, writeFilePath);JavasistUtils.printAnno(bean);TargetBean bean2 = new TargetBean();JavasistUtils.printAnno(bean2);}
}运行结果:
val:
val:
val:

4、修改后的TargetBean.classs:

public class TargetBean {private String val;@TestAnno(name = "change1",type = "1")private String val2;public TargetBean() {}public TargetBean(String val) {this.val = val;}public String getVal() {return this.val;}public void setVal(String val) {this.val = val;}
}

我们可以重新加载TargetBean类:

public class GetBean {public static void main(String[] args) {TargetBean targetBean = new TargetBean();JavasistUtils.printAnno(targetBean);}
}运行结果:
val:
val2:@com.ymqx.动态增加属性和注解.TestAnno(type=1, name=change1)

再次获取TargetBean类就是修改后的了,字段和注解都新增成功。

引申一

使用Byte Buddy重新转换类。

public static void addFields(Object obj, Map<String, Map<String, Map<String, String>>> properties, String writeFilePath) {...//生成字节码文件,方便查看创建出来的类的结果if (writeFilePath != null) {//ctClass.writeFile(writeFilePath);}//解决方案二:使用Byte Buddy字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类retransformClass(obj.getClass(), ctClass.toBytecode());} catch (Exception e) {e.printStackTrace();}
}运行结果:
val:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)at com.ymqx.动态增加属性和注解.JavasistUtils.retransformClass(JavasistUtils.java:250)at com.ymqx.动态增加属性和注解.JavasistUtils.addFields(JavasistUtils.java:141)at com.ymqx.动态增加属性和注解.TestBean.main(TestBean.java:65)

报错:class redefinition failed: attempted to change the schema (add/remove fields),类重新定义失败:尝试更改架构(添加/删除字段)。 该方法新增注解可以,但是新增字段不行。 ( ╯□╰ )

附件

最后附完整工具类 JavasistUtils:

public class JavasistUtils {/*** 新增指定属性的指定注解** @param clazz     类对象* @param fieldName 类属性* @param annotationClass 注解类对象* @param initAnnotation  注解属性*/public static void addAnnotationToField(Class<?> clazz, String fieldName, Class<?> annotationClass,BiConsumer<Annotation, ConstPool> initAnnotation) {ClassPool pool = ClassPool.getDefault();CtClass ctClass;try {ctClass = pool.getCtClass(clazz.getName());if (ctClass.isFrozen()) {ctClass.defrost();}CtField ctField = ctClass.getDeclaredField(fieldName);ConstPool constPool = ctClass.getClassFile().getConstPool();Annotation annotation = new Annotation(annotationClass.getName(), constPool);if (initAnnotation != null) {initAnnotation.accept(annotation, constPool);}AnnotationsAttribute attr = getAnnotationsAttributeFromField(ctField);if (attr == null) {attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);ctField.getFieldInfo().addAttribute(attr);}attr.addAnnotation(annotation);/* 生成字节码文件,方便查看创建出来的类的结果 *//*String path = System.getProperty("user.dir") + "\\target\\classes";ctClass.writeFile(path);ctClass.defrost();*/retransformClass(clazz, ctClass.toBytecode());} catch (NotFoundException | CannotCompileException | IOException e) {e.printStackTrace();}}/*** 功能:动态的给类属性添加注解(若注解存在,则更新;注解不存在,则新增)** @param obj           对象* @param FieldName     类属性* @param annoName      注解名称**/public static void addAnnotation(Object obj, String FieldName,  String annoName, String key, String value) {//获取类池ClassPool pool = ClassPool.getDefault();try {//获取实体类CtClass ctClass = pool.get(obj.getClass().getName());//class File这个术语定义于虚拟机规范3.1,指的是字节码的byte数组,而不是文件系统中的class文件。ClassFile classFile = ctClass.getClassFile();//获取常量池ConstPool constPool = classFile.getConstPool();//获取属性CtField ctField = ctClass.getField(FieldName);//获取属性字段信息FieldInfo fieldInfo = ctField.getFieldInfo();//创建要添加的注解Annotation anno = new Annotation(annoName, constPool);//给指定的注解添加属性anno.addMemberValue(key, new StringMemberValue(value, constPool));//获取属性字段的注解集 AnnotationsAttributeAnnotationsAttribute annosAttribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag);if (null == annosAttribute) {//字段不存在注解集,新建注解集。annosAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);//把新增注解放到一个AnnotationsAttribute对象里面annosAttribute.addAnnotation(anno);//把 AnnotationsAttribute 对象添加到要打上这个注解的字段/类上面fieldInfo.addAttribute(annosAttribute);} else {//字段存在注解集,将新增注解加入原有 AnnotationsAttribute 对象//判断注解是否存在Annotation annotation = annosAttribute.getAnnotation(annoName);if (null != annotation) {//注解存在,获取注解原来属性值for (String memberName : annotation.getMemberNames()) {//System.out.println("memberName=" + memberName + ",getMemberValue=" + annotation.getMemberValue(memberName));anno.addMemberValue(memberName, annotation.getMemberValue(memberName));}anno.addMemberValue(key, new StringMemberValue(value, constPool));}annosAttribute.addAnnotation(anno);}/*将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;通过类加载器加载该CtClass。注:调用ctClass.toClass();会报错:ClassFormatError:attempted  duplicate class definition(试图创建重复的类定义)。因为重复将TestBean加载。解决方案一:创建一个新类,让老类作为新类的Superclass。*///ctClass.toClass();//根据CtClass生成 .class 文件//ctClass.writeFile();//解决方案二:使用Byte Buddy字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类retransformClass(obj.getClass(), ctClass.toBytecode());} catch (Exception e) {e.printStackTrace();}}/*** 删除指定属性的指定注解** @param clazz     类对象* @param fieldName 类属性* @param annotationClass 注解类对象*/public static void removeAnnotationFromField(Class<?> clazz, String fieldName, Class<?> annotationClass) {ClassPool pool = ClassPool.getDefault();CtClass ctClass;try {ctClass = pool.getCtClass(clazz.getName());if (ctClass.isFrozen()) {ctClass.defrost();}CtField ctField = ctClass.getDeclaredField(fieldName);AnnotationsAttribute attr = getAnnotationsAttributeFromField(ctField);if (attr != null) {attr.removeAnnotation(annotationClass.getName());}retransformClass(clazz, ctClass.toBytecode());} catch (NotFoundException | IOException | CannotCompileException e) {e.printStackTrace();}}/*** 功能:动态添加字段和注解**/public static void addFields(Object obj, Map<String, Map<String, Map<String, String>>> properties, String writeFilePath) {//获取类池ClassPool pool = ClassPool.getDefault();try {//获取实体类CtClass ctClass = pool.get(obj.getClass().getName());//class File这个术语定义于虚拟机规范3.1,指的是字节码的byte数组,而不是文件系统中的class文件。ClassFile classFile = ctClass.getClassFile();//获取常量池ConstPool constPool = classFile.getConstPool();for (String fieldKey : properties.keySet()) {//System.out.println("fieldKey=" + fieldKey);CtField ctField = new CtField(pool.get(String.class.getCanonicalName()), fieldKey, ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);// 新增注解属性池AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);for (String annoKey : properties.get(fieldKey).keySet()) {//System.out.println("annoKey=" + annoKey);//创建要添加的注解Annotation anno = new Annotation(Class.forName(annoKey).getCanonicalName(), constPool);//设置注解中的属性和值properties.get(fieldKey).get(annoKey).forEach((k, v) -> {//System.out.println("k=" + k + ",v=" + v);anno.addMemberValue(k, new StringMemberValue(v, constPool));});//把这个注解放到一个AnnotationsAttribute对象里面annotationsAttribute.addAnnotation(anno);}//把这个对象放在要打上这个注解的字段/类上面ctField.getFieldInfo().addAttribute(annotationsAttribute);}//生成字节码文件,方便查看创建出来的类的结果if (writeFilePath != null) {//ctClass.writeFile(writeFilePath);}//解决方案二:使用Byte Buddy字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类retransformClass(obj.getClass(), ctClass.toBytecode());} catch (Exception e) {e.printStackTrace();}}/*** 功能:动态创建类并添加注解**/public static void createBean(String className, Map<String, Map<String, Map<String, String>>> properties, String writeFilePath) {ClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass(className);//让该类实现序列化接口ctClass.setInterfaces(new CtClass[]{pool.makeInterface("com.ymqx.动态增加属性和注解.IExcelModel"),pool.makeInterface("java.io.Serializable")});StringBuilder builder = new StringBuilder();builder.append("return \"Person{\" + \n  " );try {for (String fieldKey : properties.keySet()) {//System.out.println("fieldKey=" + fieldKey);CtField ctField = new CtField(pool.get(String.class.getCanonicalName()), fieldKey, ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);// 类的字节码文件ClassFile classFile = ctClass.getClassFile();// 获取常量池ConstPool constPool = classFile.getConstPool();// 新增注解属性池AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);for (String annoKey : properties.get(fieldKey).keySet()) {//System.out.println("annoKey=" + annoKey);//创建要添加的注解Annotation anno = new Annotation(Class.forName(annoKey).getCanonicalName(), constPool);//设置注解中的属性和值properties.get(fieldKey).get(annoKey).forEach((k, v) -> {//System.out.println("k=" + k + ",v=" + v);anno.addMemberValue(k, new StringMemberValue(v, constPool));});//把这个注解放到一个AnnotationsAttribute对象里面annotationsAttribute.addAnnotation(anno);}//把这个对象放在要打上这个注解的字段/类上面ctField.getFieldInfo().addAttribute(annotationsAttribute);//添加getter setter方法ctClass.addMethod(CtNewMethod.setter("set" + fieldKey.substring(0, 1).toUpperCase() + fieldKey.substring(1), ctField));ctClass.addMethod(CtNewMethod.getter("get" + fieldKey.substring(0, 1).toUpperCase() + fieldKey.substring(1), ctField));//组装toString方法体String format = String.format("\"%s='\" + %s + '\\'' + ',' +\n", fieldKey, fieldKey);builder.append(format);}if ( builder.length()>0 && (-1 != builder.lastIndexOf(",")) ) {builder.setCharAt(builder.lastIndexOf(","), ' ');}builder.append("'}';");//添加toString方法CtMethod toStringMethod = new CtMethod(pool.get("java.lang.String"), "toString", null, ctClass);toStringMethod.setBody(builder.toString());ctClass.addMethod(toStringMethod);//生成字节码文件,方便查看创建出来的类的结果if (writeFilePath != null) {ctClass.writeFile(writeFilePath);}//也可以用这种方式,不用生成.class文件也可以直接使用动态生成的类ctClass.toClass(ClassPool.getDefault().getClassLoader(), Class.class.getProtectionDomain());} catch (Exception e) {e.printStackTrace();}}/*** 遍历打印对象的注解值** @param obj  对象*/public static void printAnno(Object obj) {// 遍历获取Field[] 集合Field[] declaredFields = obj.getClass().getDeclaredFields();for (Field f : declaredFields) {// 遍历获取Annotation[] 集合System.out.println(f.getName() + ":");java.lang.annotation.Annotation[] annotations = f.getDeclaredAnnotations();for (java.lang.annotation.Annotation anno : annotations) {System.out.println("\t" + anno);}}}/*** 遍历指定属性下所有注解** @param obj       对象* @param FieldName 类属性*/public static void queryAnnotation(Object obj, String FieldName) {//System.out.println("====开始遍历注解所有属性=====");try {ClassPool pool = ClassPool.getDefault();//获取实体类CtClass ct = pool.get(obj.getClass().getName());if (ct.isFrozen()) {ct.defrost();}//获取属性CtField cf = ct.getField(FieldName);//获取属性字段信息FieldInfo fieldInfo = cf.getFieldInfo();//获取属性字段的运行时可见注解AnnotationsAttribute attribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag);if (null != attribute) {//获取所有注解Annotation[] annotations = attribute.getAnnotations();int sum = 0;//遍历注解for (Annotation annotation : annotations) {System.out.println("注解" + (++sum) + ":" + annotation.toString());//如果没有属性名,就下一个循环if (annotation.getMemberNames() == null) {System.out.println("!无属性名跟属性值!");continue;}//获取注解的所有属性名、属性值for (String memberName : annotation.getMemberNames()) {/*System.out.println("获取到的注解的属性名:" + memberName);System.out.println("获取到的注解的属性值:" + annotation.getMemberValue(memberName));*/}}} else {System.out.println("未获取到属性字段的注解");}} catch (NotFoundException e) {System.out.println("此类不存在" + e);}}/*** 获取指定属性的注解集合** @param ctField 类属性*/private static AnnotationsAttribute getAnnotationsAttributeFromField(CtField ctField) {List<AttributeInfo> attrs = ctField.getFieldInfo().getAttributes();AnnotationsAttribute attr = null;if (attrs != null) {Optional<AttributeInfo> optional = attrs.stream().filter(AnnotationsAttribute.class::isInstance).findFirst();if (optional.isPresent()) {attr = (AnnotationsAttribute) optional.get();}}return attr;}/*** 使用Byte Buddy重新转换类** @param clazz    类对象* @param byteCode 类文件的二进制数组*/private static void retransformClass(Class<?> clazz, byte[] byteCode) {ClassFileTransformer cft = new ClassFileTransformer() {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) {return byteCode;}};Instrumentation instrumentation = ByteBuddyAgent.install();try {instrumentation.addTransformer(cft, true);instrumentation.retransformClasses(clazz);} catch (UnmodifiableClassException e) {e.printStackTrace();} finally {instrumentation.removeTransformer(cft);}}
}

Javassist实战-修改现有类相关推荐

  1. 2.在某应用软件中需要记录业务方法的调用日志,在不修改现有业务类的基础上为每一个类提供一个日志记录代理类,在代理类中输出日志,例如在业务方法 method() 调用之前输出“方法 method() 被

    2.在某应用软件中需要记录业务方法的调用日志,在不修改现有业务类的基础上为每一个类提供一个日志记录代理类,在代理类中输出日志,例如在业务方法 method() 调用之前输出"方法 metho ...

  2. 29.怎样扩展现有类功能?

    实际项目开发中,我们经常会需要对系统的UI控件等进行封装,以达到统一修改.重复代码少.复用性高等效果.OC中,我们一般通过Category来给现有类添加方法:而在Swift中,我们使用Extensio ...

  3. 软件设计的哲学:第十六章 修改现有代码

    目录 16.1 保持战略 16.2 维护注释:将注释放在代码附近 16.3 注释属于代码,而不是提交日志 16.4 保留注释:避免重复 16.5 维护注释:检查差异 16.6 更高级别的注释更容易维护 ...

  4. R语言dplyr包的mutate函数将列添加到dataframe中或者修改现有的数据列:基于条件判断创建布尔型指示变量、将异常离散编码转化为NA值

    R语言dplyr包的mutate函数将列添加到dataframe中或者修改现有的数据列:基于条件判断创建布尔型指示变量.将异常离散编码转化为NA值 目录

  5. R语言dplyr包的mutate函数将列添加到dataframe中或者修改现有的数据列:使用na_if()函数将0值替换为NA值、负收入替换为NA值

    R语言dplyr包的mutate函数将列添加到dataframe中或者修改现有的数据列:使用na_if()函数将0值替换为NA值.负收入替换为NA值 目录

  6. java.lang.Instrument 动态修改替换类代码

    java.lang.Instrument 动态修改替换类代码 | java.lang.Instrument包是在JDK5引入的,程序员通过修改方法的字节码实现动态修改类代码. 这通常是在类的main方 ...

  7. SAP UI5 应用开发教程之三十六 - 使用 Chrome 开发者工具 Elements 标签动态修改 CSS 类试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  8. java 修改 枚举类字段_枚举枚举和修改“最终静态”字段的方法

    java 修改 枚举类字段 在本新闻通讯中,该新闻通讯最初发表在Java专家的新闻通讯第161期中,我们研究了如何使用sun.reflect包中的反射类在Sun JDK中创建枚举实例. 显然,这仅适用 ...

  9. 程序功能:创建打印机类Printer,定义抽象方法Print()。 创建针式打印机类DotMatrixtPrinter和墨式打印机InkpetPrinter两个子类,修改测试类,实现该打印机打印。

    程序功能:创建打印机类Printer,定义抽象方法Print(). 创建针式打印机类DotMatrixtPrinter和墨式打印机InkpetPrinter两个子类,并在各自类中重新print方法,编 ...

最新文章

  1. LFCS 系列第二讲:如何安装和使用纯文本编辑器 vi/vim
  2. 面试题整理 4 合并两个排序的数组
  3. 说说 Spring AOP 原理
  4. [LeedCode]921. 使括号有效的最少添加
  5. 经典SQL语句大全(1)
  6. JavaScript之路(3)JavaScript的函数
  7. 直角三角形 纪中 1385 数学_斜率 英文题解
  8. Android关闭输入法键盘
  9. DelphiXE开发坑
  10. 中国计算机学会(CCF)推荐中文科技期刊目录
  11. 程序员校招社招简历模板分享和硬技能
  12. 微信小程序 tabs 选项卡 分页加载 + 图片上传
  13. p2p-如何拯救k8s镜像分发的阿喀琉斯之踵 1
  14. Python标记函数或类为废弃(deprecated)并在Pychram或Idea中检测提示删除线
  15. UE4UE5 VR开发多人联机RPC的坑
  16. “以简驭繁”打造卓越校园网 ——锐捷助力西安理工大学校园网建设
  17. Python的字符串比较
  18. Android 插件化之—— 加载插件中的资源
  19. Android键盘监听事件
  20. elmah_ELMAH:ASP.NET的错误记录模块和处理程序(也包括MVC!)

热门文章

  1. Qt简单编程--数组保存到文件
  2. 是时候不得不学英语了,技多不压身,给自己多条路
  3. AI 实战篇 |十分钟学会【动物识别】,快去寻找身边的小动物试试看吧【送书】
  4. 前端学习2——CSS3
  5. 【转】“蓝海战略”:企业发展战略新思维
  6. 蓝海战略与 Web 2.0
  7. 中国移动gprs上网设置
  8. Mac Option 键的妙用,原来有这么多功能
  9. Java并发编程一万字总结(吐血整理)
  10. android webview访问本地文件