56

I have a simple marker annotation for methods (similar to the first example in Item 35 in Effective Java (2nd ed)):

/**
 * Marker annotation for methods that are called from installer's 
 * validation scripts etc. 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InstallerMethod {
}

Then, in a given package (say com.acme.installer), which has a few subpackages containing some 20 classes, I'd like to find all methods that are annotated with it. (Because I'd like to do some checks regarding all the annotated methods in a unit test.)

What (if any) is the easiest way to do this? Preferably without adding new 3rd party libraries or frameworks.

Edit: to clarify, obviously method.isAnnotationPresent(InstallerMethod.class) will be the way to check if a method has the annotation - but this problem includes finding all the methods.

Jonik
  • 80,077
  • 70
  • 264
  • 372
  • Check http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection – victor hugo May 14 '09 at 08:24
  • 2
    Hmm, yeah, I guess this comes back to that question (which I asked in February). :P But whereas the right answer there was "no, it cannot be done using reflection", here the question is *how* to find annotated methods most easily, using whatever means... – Jonik May 14 '09 at 08:33
  • 2
    Because the difficult part here isn't really related to annotations (but finding classes from package), this question didn't turn out as relevant as I thought. :/ Oh well, perhaps this could still help someone wondering about the same thing... – Jonik May 14 '09 at 15:13

3 Answers3

55

If you want to implement it yourself, these methods will find all the classes in a given package:

/**
 * Scans all classes accessible from the context class loader which belong
 * to the given package and subpackages.
 * 
 * @param packageName
 *            The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private Iterable<Class> getClasses(String packageName) throws ClassNotFoundException, IOException
{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements())
    {
        URL resource = resources.nextElement();
        URI uri = new URI(resource.toString());
        dirs.add(new File(uri.getPath()));
    }
    List<Class> classes = new ArrayList<Class>();
    for (File directory : dirs)
    {
        classes.addAll(findClasses(directory, packageName));
    }

    return classes;
}

/**
 * Recursive method used to find all classes in a given directory and
 * subdirs.
 * 
 * @param directory
 *            The base directory
 * @param packageName
 *            The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException
{
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists())
    {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files)
    {
        if (file.isDirectory())
        {
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        }
        else if (file.getName().endsWith(".class"))
        {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

Then you can just filter on those classes with the given annotation:

for (Method method : testClass.getMethods())
{
    if (method.isAnnotationPresent(InstallerMethod.class))
    {
        // do something
    }
}
parkr
  • 3,188
  • 5
  • 35
  • 39
  • Thanks! Using this I was able to do what I wanted. It's pretty much code, but perhaps there just isn't a simpler way (without external libs), with classloader working the way it does. – Jonik May 14 '09 at 09:18
  • 5
    Thanks for sharing. I used this code as a basis for my own solution, although others should note that more work is necessary if you're looking for classes in a JAR file. – Mick Sear Dec 31 '13 at 14:04
  • @MickSear I am interested exactly in finding classes inside jar files. Could you give me hints on the extra work needed? – usr-local-ΕΨΗΕΛΩΝ Mar 01 '16 at 11:18
  • @OndraŽižka The snippet is no longer at your link. – AlbeyAmakiir Feb 14 '18 at 05:57
  • @parkr Thanks a lot. This saved lot of my time. I had the same approach but was getting duplicate packages while forming the fully qualified name. – Sai Manoj Kadiyala May 21 '21 at 01:06
37

You should probably take a look at the open source Reflections library. With it you can easily achieve what you want with few lines of code:

Reflections reflections = new Reflections( 
    new ConfigurationBuilder().setUrls( 
    ClasspathHelper.forPackage( "com.acme.installer" ) ).setScanners(
    new MethodAnnotationsScanner() ) );
Set<Method> methods = reflections.getMethodsAnnotatedWith(InstallerMethod.class);
Aleksander Blomskøld
  • 18,374
  • 9
  • 76
  • 82
  • 5
    Are you sure this works? Try it out, I don't think it does. I had to set the scanner in the Reflections class constructor to get it to work, like this: `Reflections reflections = new Reflections( new ConfigurationBuilder().setUrls( ClasspathHelper.forPackage( "com.acme.installer" ) ).setScanners( new MethodAnnotationsScanner() ) );` – javamonkey79 Aug 10 '12 at 23:28
  • 1
    Oh, you're right, my original snippet didn't work. Thanks for letting me know! – Aleksander Blomskøld Aug 14 '12 at 14:35
1

If you're happy to use Spring, then that does something along these lines using it's context:component-scan functionality, where Spring scans for annotated classes in a given package. Under the covers, it's pretty gruesome, and involves grubbing about on the filesystem and in JAR files looking for classes in the package.

Even if you can't use Spring directly, having a look through its source code might give you some ideas.

Certainly, the Java reflection APi is no use here, it specifically does not provide a means to obtain all classes in a package.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • Thanks. Hmm, you say "Spring scans for annotated classes" - can you use it in this case where we're looking for annotated *methods*, even in classes with no annotations? Or, alternatively, if you would implement a method like getClasses(String packageName) from parkr's answer making use of Spring, could it be made much simpler? – Jonik May 14 '09 at 10:15
  • The Spring code looks for all classes in that package, and then looks for class and method level annotations. You may be able to make it simpler in certain environments, but not hugely so. – skaffman May 14 '09 at 10:46