Javassist实战-修改现有类

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

修改现有.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() {
            @Override
            public 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));

            //获取属性字段的注解集 AnnotationsAttribute
            AnnotationsAttribute 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() {
            @Override
            public 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);
        }
    }
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值