对于新增类应用场景不常见,而修改现有类应用场景更多,比如常见的日志切面,权限切面。
修改现有.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);
}
}
}