How to find annotated methods in a given package?
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.
Solution 1:
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
}
}
Solution 2:
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);
Solution 3:
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.