我有以下代码用于动态添加注释到java类
private void decorateWithSpecificAnnotation(final Set<Class<?>> domainClasses) {
final ClassPool cp = ClassPool.getDefault();
for (Class<?> c : domainClasses) {
try {
final CtClass cc = cp.get(c.getName());
final ClassFile cfile = cc.getClassFile();
final ConstPool cpool = cfile.getConstPool();
final AnnotationsAttribute attr = new AnnotationsAttribute(cc.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag);
final Annotation annot = new Annotation(Document.class.getName(), cpool);
attr.addAnnotation(annot);
cfile.addAttribute(attr);
} catch (NotFoundException e) {
throw new RuntimeException("Unexpected error occured during dynamic domain decoration", e);
}
}
}
在我执行以下操作后调用上述方法后,我看到该注释不存在。我错过了什么。
decorateWithSpecificAnnotation(domainClasses);
domainClasses.stream().forEach(d -> {
System.out.println(d.isAnnotationPresent(Document.class));
});
答案 0 :(得分:0)
当您使用Class<?>
对象时,该类已经由类加载器加载,因此您无法修改它。您可以使用另一个类加载器再次加载该类(但您只能使用该类的类)。你可以重新加载这个类,但这不是那么简单,甚至可能是必要的事情。
如果你传递一个带有完全限定类名的Set<String>
,而不使用那些类(在以前的代码的任何部分中),它就可以工作。
修改类的操作通常是应该执行的第一个过程。
您可以使用一些库来扫描类路径,而无需加载类,例如fast-classpath-scanner。
<dependency>
<groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId>
<version>${fast-classpath.version}</version>
</dependency>
要使用它,您可以指定要扫描的基础包(它以递归方式工作):,然后您将再次重新过滤该类(以确保仅使用该包的类,因为它还可以检测某些类是不在该包中,如注释),过滤package-info
和其他自定义过滤器。
此方法查找其上带有注释CustomAnnotation
的所有类。
Set<String> classNames = new FastClasspathScanner(basePackage)
.scan().getNamesOfAllClasses()
.getNamesOfClassesWithAnnotationsAnyOf(CustomAnnotation.class)
.stream()
.filter(name -> name.startsWith(basePackage))
.filter(name -> !name.endsWith("package-info"))
.collect(Collectors.toSet());
编辑:
您还需要调用用于加载类的方法:
cc.toClass();
最后,如果你想保留previos注释,你不应该创建一个新的AnnotationsAttribute
对象,而是通过以下方式检索当前:
AnnotationsAttribute attr = (AnnotationsAttribute) cfile
.getAttribute(AnnotationsAttribute.visibleTag)
答案 1 :(得分:0)
您的代码朝着正确的方向前进,但问题是您需要将CtClass转换回Java类。正如@jose-da-silva所提到的,你不能简单地重新加载已经加载的类,但你可以重新转换它。
注释是一种特殊情况。可以使用Instrumentation在运行时重新转换类。 Javadoc引用的重新转换的限制是:
重新转换可能会改变方法体,常量池和 属性。重新转换不得添加,删除或重命名字段 或方法,更改方法的签名或更改继承。
注释驻留在常量池中,因此它们不受此限制。请参阅此示例名为AnnotationBuilder。它是使用Javassist修改类的更大库的一部分。获取Instrumentation实例有点涉及,因此本示例仅使用ByteBuddy来获取实例。这是用法的简要示例:
public static void main(String[] agrs) {
log("Quickie Test");
AnnotationBuilder ab = newBuilder("javax.xml.ws.soap.Addressing")
.add("enabled", false)
.add("required", true)
.add("responses", Responses.NON_ANONYMOUS);
try {
final ClassPool cp = new ClassPool();
cp.appendSystemPath();
cp.appendClassPath(new LoaderClassPath(TestClass.class.getClassLoader()));
final CtClass ctclazz = cp.get(TestClass.class.getName());
ab.applyTo(ctclazz);
final byte[] byteCode = ctclazz.toBytecode();
final String target = TestClass.class.getName().replace('.', '/');
log("Target:" + target);
final ClassFileTransformer cft = new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
log("ClassName:" + className);
if(target.equals(className)) {
log("Match !");
return byteCode;
}
return null;
}
};
// ==============================================================================
// There's several ways to acquire an Instrumentation instance
// This one is the easiest
// ==============================================================================
Instrumentation ins = ByteBuddyAgent.install();
try {
ins.addTransformer(cft, true);
ins.retransformClasses(TestClass.class);
} finally {
ins.removeTransformer(cft);
}
log("Done");
Addressing addr = TestClass.class.getAnnotation(Addressing.class);
log("Annotation Present:" + addr!=null);
if(addr!=null) {
log("Enabled:" + addr.enabled());
log("Required:" + addr.required());
log("Responses:" + addr.responses().name());
}
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}