使用javassist添加注释以在运行时键入

时间:2018-03-15 15:09:13

标签: java java-8 java-bytecode-asm javassist

我有以下代码用于动态添加注释到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));
});

2 个答案:

答案 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);
    }       
}