» Articles » How to scan for annotated classes and instantiate them

How to scan for annotated classes and instantiate them

You may have been faced with the following problem: Let’s say you have a system that provides a couple of neat features out of the box, but you want to provide some means for the end user to extend the system, without having to release a new version that incorporates what your users have added (maybe because those additions are too exotic, or you just don’t like it).

Goal: Let’s create a system that can be dynamically extended by way of adding annotated classes to the classpath.

First of all, let’s define the annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Huep {}

It’s important to use RetentionPolicy.RUNTIME since we want to query for annotations at runtime. Now we can annotate a class:

@Huep
public class SomeClass implements HuepInterface {
// implementing HuepInterface
}

That’s it, now we have something we can hunt for: Classes annotated woth ‘@Huep’ will be added dynamically to our system.

Now all we need is to scan all classes within the classpath and look for our annotation. This usually requires us to get the current classcloader and scan all jars and classes listed in the URLs.

Something like this:

ClassLoader classLoader = getClass().getClassLoader();
URLClassLoader uc = null;
if (classLoader instanceof URLClassLoader) {
    uc = (URLClassLoader)classLoader;
}
else {
    throw new RuntimeException("classLoader is not an instanceof URLClassLoader");
}
URL[] urLs = uc.getURLs();
// just have a look at what you get...
for (int i = 0; i < urLs.length; i++) {
    System.out.println( i + ". " + urLs[i]);
}

Note that above example (which only looks at the system classloader) is not correct in some environments (containers, maven, ant, etc.). We may need to look at the context classloader as well. Therefore, the proper list of urls to scan through looks like this:

ArrayList<URL> urls = new ArrayList<URL>();
ClassLoader [] classloaders = {
        getClass().getClassLoader(),
        Thread.currentThread().getContextClassLoader()
};
for (int i = 0; i < classloaders.length; i++) {
    if (classloaders[i] instanceof URLClassLoader) {
        urls.addAll(Arrays.asList(((URLClassLoader)classloaders[i]).getURLs()));
    }
    else {
        throw new RuntimeException("classLoader is not an instanceof URLClassLoader");
    }
}

Going through the list and scanning all the archives is tedious. Luckily there are very nice libraries available that do the job for us. We’ll use reflections

Setting up reflections can be fairly easy…

ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.setUrls(urls);
Reflections reflections = new Reflections(configurationBuilder); 

… but this wouldn’t be very efficient since we would now scan absolutely everything in our classpath, which could take a while. While reflections provides a nifty save() feature to store the result…

reflections.save("reflections.store");

… you could also limit the scope of scanning by way of defining filters. This greatly increases performance.

String EXTERNAL_PACKAGE = "external_package";
String filter = "com.acme.*";
Predicate<String> filters = null;
if (System.getProperty(EXTERNAL_PACKAGE) != null) {
    String userFilter = System.getProperty(EXTERNAL_PACKAGE);
    filters = new FilterBuilder().include(userFilter).include(filter);
}
else {
    filters = new FilterBuilder().include(filter);
}
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.filterInputsBy(filters);
configurationBuilder.setUrls(urls);
Reflections reflections = new Reflections(configurationBuilder); 

The ‘external_package’ system property can be used to add a package under which new annotated classes can be found.

The reflections-object can be used to query for classes, e.g. for subtypes of a class, classes with annotations, etc. Unfortunately, in my environment it invariably led to a java.lang.ClassNotFoundException.

Therefore, I had to go with the following solution:

// this leads to ClassNotFoundException :(
// reflections.getTypesAnnotatedWith(Huep.class);
// but this works:
Set<String> myclassnames = reflections.getStore().getTypesAnnotatedWith("Huep");
for (Iterator<String> iterator = myclassnames.iterator(); iterator.hasNext();) {
    String myName = (String) iterator.next();
    Class<?> myClass = null;
    try {
        myClass = Class.forName(myName, true, Thread.currentThread().getContextClassLoader() );
    }
    catch (ClassNotFoundException e) {
        try {
            myClass = Class.forName(myName);
        }
        catch (ClassNotFoundException f) {
            LOG.error("exception caught:", e);
            LOG.error("my {} is not working!", myName);
        }
    }
    HuepInterface huep = (HuepInterface)parserClass.newInstance();
}

That’s it!