3

I'm using Reflections to find classes that have an specific annotation. My project structure is the following

One WAR package: WEB-INF/classes/...packages.../ClassAnnoted1.class

One JAR package that is included by the war that has a class that executes this code:

Reflections reflections= new Reflections(ClasspathHelper.forWebInfClasses(servletContext))
Set set= reflections.getTypesAnnotatedWith(CustomAnnotation.class)

CustomAnnotation is also present on the JAR package.

the set size is correct (ie if I have 3 classes with the annotation in my WAR the jar, the set size comes back as 3), but all elements inside it are null instead of Class. I need to get the class and check the annotation parameters inside the class of the JAR.

Anyone got any idea of why this is happening?

EDIT:

Reflections reflections= new Reflections("com.my.customAnnotededClasses"); //package that my annoted class is in
Set set= reflections.getTypesAnnotatedWith(CustomAnnotation.class);

Also does not work, in this case the set length is zero instead of the number of classes with the annotation.

EDIT 2: Ok, the real problem was that I was packaging my whole application as an EAR so I had the following:

EAR
----> WAR
----> JAR

The jar was included in the EAR lib folder and not on the WAR lib folder. So the jar classes couldn't see the war classes, once i made the WAR depend on the JAR directly like this:

EAR
----> WAR
---------> JAR

It started working. But the original question still stands, there might be situations where I want the Jar classes included in the EAR instead of the WAR (if i have multiple wars that need to use my jar for instance).

Hoffmann
  • 14,369
  • 16
  • 76
  • 91

4 Answers4

2

I guess I can't do it using the reflections library. So I did it by hand:

public static List<Class<?>> getClassesAnnotatedWith(Class annotation, ServletContext servletContext) {
    List<Class<?>> webClasses, jarClasses;
    webClasses= getClassesAnnotedWithFromClassLoader(annotation, servletContext.getClassLoader());
    jarClasses= getClassesAnnotedWithFromClassLoader(annotation, Thread.currentThread().getContextClassLoader());
    for (Class<?> jarClass : jarClasses) {
        Class<?> elementToAdd= null;
        for (Class<?> webClass : webClasses) {
            if ( ! jarClass.getName().equals(webClass.getName())) {
                elementToAdd= jarClass;
            }
        }
        if(elementToAdd != null) {
            webClasses.add(elementToAdd);
        }
    }
    return webClasses;
}


 private static List<Class<?>> getClassesAnnotedWithFromClassLoader(Class annotation, ClassLoader classLoader) {
        List<Class<?>> classes= new ArrayList<Class<?>>();
        Class<?> classLoaderClass= classLoader.getClass();
        while (! classLoaderClass.getName().equals("java.lang.ClassLoader")) {
            classLoaderClass= classLoaderClass.getSuperclass();
        }
        try {
            Field fldClasses= classLoaderClass.getDeclaredField("classes");
            fldClasses.setAccessible(true);
            Vector<Class<?>> classesVector= (Vector<Class<?>>) fldClasses.get(classLoader);
            for (Class c : classesVector) {
                if (c.isAnnotationPresent(annotation)) {
                    classes.add(c);
                }
            }
        } catch (Exception e) { }
        return classes;
    }

I get the ClassLoader from my WAR package through the ServletContext object. There is also a protection in case a class is defined in both the WAR and the JAR with the annotation and same name (you should probably check if the packages are the same too though).

Note that you should probably never use this code in your own projects (maybe only for debugging). It involves reflecting the ClassLoader class to make the "classes" property public. This property might not exists in Java 9 for example, so beware. This might also have some security problems if you are interacting modules written by third parties.

Hoffmann
  • 14,369
  • 16
  • 76
  • 91
  • Like your answer - gives full power of reflection API and can be reused/hacked for many other purposes in future. But it is the solution described by wrm's comments, so I hope you give bounty to wrm & not yourself. Cheers :^) – Glen Best Apr 10 '13 at 03:46
  • 'classLoaderClass.getDeclaredField("classes")' is problematic – zapp Apr 15 '13 at 07:45
  • @zapp I know it's far from a good solution, but why exactly is this a bad idea? – Hoffmann Apr 15 '13 at 13:47
1

i had one a similar problem. are you sure, you included the annotation-classes into your classpath? if they are not loaded, they will somehow be found but not really returned and without any exception or anything

wrm
  • 1,898
  • 13
  • 24
  • yes, the annotation is there. I actually have a class that use it on the JAR package and I can get that class, but the classes on the WAR package are returning null. – Hoffmann Apr 08 '13 at 12:32
  • still sounds like a classpath-problem to me....have you tried cutting out "reflections" and use direct java-reflection to list the annotations on your class? It could also be that different classloaders loaded the annotation-class. i.e.war and jar used different classloaders. – wrm Apr 08 '13 at 12:52
  • I suspect that the classloaders are the problem, but I'm not that experienced with classloaders and reflection in Java so I'm not entirely sure. How would I go about doing that without using the reflections library? – Hoffmann Apr 08 '13 at 13:37
  • You know how to get the WAR classloader from inside the JAR classes? I'm packaging my application as an EAR with the WAR and the JAR inside it. – Hoffmann Apr 08 '13 at 13:45
  • you can load your class with getClass().getClassLoader().loadClass(...) and then extract your annotation like this: http://tutorials.jenkov.com/java-reflection/annotations.html#class you can experiment with the threadclassloader to see, if it works... (use `Thread.currentThread().getContextClassLoader()`) – wrm Apr 08 '13 at 14:03
  • I managed to get the WEB classloader using servletContext.getClassLoader() (the servletContext runs on the WEB package, I pass it to the method that runs my relfection code that is on the JAR). With that classloader you know how to get the classes annotated with my CustomAnnotation? – Hoffmann Apr 08 '13 at 14:23
  • The thing is I don't know which classes will be present on the WEB project, there can be none or dozens of classes with my CustomAnnotation present. – Hoffmann Apr 08 '13 at 14:24
  • i know... just as a test... load a class that you know that it exist... if it contains annotations, you have to feed this classloader into reflections somehow... – wrm Apr 09 '13 at 07:43
  • The only way I found to load a class from the WAR project was to get its ClassLoader (using the ServletContext object) and load it using that ClassLoader, the JAR ClassLoader can't see the WAR classes. – Hoffmann Apr 09 '13 at 12:25
0

The Reflections library gave me various problems. Now I am using the reflection part of the Guava library: until now, no unexpected behavior has occurred. In any case, I think that it is very rare that the source of the problem is the Java classloader. Maybe try to load the class CustomAnnotation.class before to use it in the Reflections API.

sandromark78
  • 148
  • 1
  • 4
0

Your code should work on conventional environments. However, in different environments, such as osgi, you get:

1) urls with different protocol (bundle/vfs/...)

2) different class loader.

In the first case, you should a) add the relevant UrlType (see the DefaultUrlTypes in Vfs for examples), or b) use different method to get the urls (see other methods in ClasspathHelper and examine the returned URL list)

In the second case, you should a) pass the customClassLoader to Reflections constructor or ConfigurationBuilder in order resolving will happen, or b) query the store directly reflections.getStore().get(TypeAnnotationsScanner.class)

see also #8339845, JbossIntegration

Community
  • 1
  • 1
zapp
  • 1,533
  • 1
  • 14
  • 18