3

I wrote a simple annotation and an AnnotationProcessor to process the annotation.

The annotation has a single value: it should be the name of an existing interface (with package).

The annotation processor should retrieve the value of the annotation, retrieve the Class object of the interface and finally should print all method declared in the interface.

Example: this is my annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation{
    public String interfaceName();
}

and this is the annotated class:

@MyAnnotation(interfaceName = "java.lang.CharSequence")
public class Example{}

my processor looks like

[...]
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {

    for (TypeElement te : annotations) {

        for(Element e : env.getElementsAnnotatedWith(te)) {

            MyAnnotation myAnnotation = e.getAnnotation(MyAnnotation.class);
            String iName = myAnnotation.interfaceName();
            Class<?> clazz = Class.forName(iName);
            // use reflection to cycle through methods and prints them
            [...]
        }
    }

now this works fine if I configure an interface like java.lang.CharSequence as the interfaceName of MyAnnotation;

however, if I configure as interfaceName an interface located in a .jar file (added in the buildpath of the project), I obtain a ClassNotFoundException when I try to do the Class.forName(...) statement.

Any thoughts?

Thank you, cheers

6
  • It may be an issue with the way annotation processing works. Does using Elements#getTypeElement(CharSequence) return a non-null TypeElement? Commented Jul 29, 2019 at 14:20
  • Hi Slaw, thank you for the response. I did TypeElement typeElement = this.processingEnv.getElementUtils().getTypeElement(myAnnotation.interfaceName()); and typeElement is not null. Commented Jul 29, 2019 at 14:28
  • 1
    In that case, you may be better off accessing information about a class through the annotation processing API (i.e. via the TypeElement). Commented Jul 30, 2019 at 3:56
  • Hi Slaw, yeah I tried to use that API, but I didn't find any method to read return type or parameter list of a method. Using Java reflection, instead, it was very easy... Commented Jul 30, 2019 at 8:33
  • 1
    To determine the parameter types and return type of a method with the annotation processor API you need to get the appropriate ExecutableElement. Check out the methods of the ElementFilter class. Note, however, that I'm not saying you have to use the annotation processor API in this case. Commented Jul 30, 2019 at 8:45

1 Answer 1

3

This is a typical ClassLoader issue. You're trying to find a class which was not loaded by the current class loader, so a ClassNotFoundException is thrown.

The Javadoc for java.lang.Class defines the method Class.forName(className) as:

Returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to Class.forName(className, true, currentLoader) where currentLoader denotes the defining class loader of the current class.

So, this method call will try to look up the specified class in the current class loader context. The interface you are trying to find has not been loaded by this classloader, so a ClassNotFoundException is thrown.

If the .jar file is on the classpath of your application, you can simply use the system classloader instead, like so...

ClassLoader systemClassLoader = ClassLoader.getSystemClassloader();
Class<?> clazz = Class.forName(className, true, systemClassLoader)

...But if your .jar file is located elsewhere, and has not been loaded yet, you're going to need to load it accordingly:

ClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("path/to/file.jar")});
Class<?> clazz = Class.forName(className, true, urlClassLoader)

Et voilà!

Sign up to request clarification or add additional context in comments.

4 Comments

Hi Daniel and thank you for the response. Ok, it's clear the issue with the Class Loader, so I changed the line of code as u suggested in Class<?> clazz = Class.forName(className, true, ClassLoader.getSystemClassLoader()); However, the problem is still there. Maybe the problem is here: "If the .jar file is on the classpath of your application, you can simply use the system classloader " I'm using Eclipse as IDE, and I added the .jar to the "buildpath", not to the "classpath" (idk the difference)...
I tried with URLClassLoader and it found the class :) However, I didn't understand why the SystemClassLoader cannot find it, the .jar is in the buildpath of the project...
@user1561017 It's a little confusing, allow me to explain. The buildpath and the classpath are two seperate things. Eclipse uses the buildpath to resolve classes inside the IDE. The buildpath is used for compilation, and provides the IDE with rich functionality. It is the classpath that matters at runtime; the classpath is what is used to resolve the loaded .class files inside the JVM. The JVM is effectively ignorant to the contents of the Eclipse buildpath. It cares only about the classpath.
Hi Daniel, thank you again for the explanation. However, this opens another question :) How can I add a given jar to the "classpath" of a project using Eclipse? I can find only the option to add a jar to the "buildpath"... thank you very much

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.