Depending on your particular requirements, in some cases Java's service loader mechanism might achieve what you're after.
In short, it allows developers to explicitly declare that a class subclasses some other class (or implements some interface) by listing it in a file in the JAR/WAR file's META-INF/services
directory. It can then be discovered using the java.util.ServiceLoader
class which, when given a Class
object, will generate instances of all the declared subclasses of that class (or, if the Class
represents an interface, all the classes implementing that interface).
The main advantage of this approach is that there is no need to manually scan the entire classpath for subclasses - all the discovery logic is contained within the ServiceLoader
class, and it only loads the classes explicitly declared in the META-INF/services
directory (not every class on the classpath).
There are, however, some disadvantages:
- It won't find all subclasses, only those that are explicitly declared. As such, if you need to truly find all subclasses, this approach may be insufficient.
- It requires the developer to explicitly declare the class under the
META-INF/services
directory. This is an additional burden on the developer, and can be error-prone.
- The
ServiceLoader.iterator()
generates subclass instances, not their Class
objects. This causes two issues:
- You don't get any say on how the subclasses are constructed - the no-arg constructor is used to create the instances.
- As such, the subclasses must have a default constructor, or must explicity declare a no-arg constructor.
Apparently Java 9 will be addressing some of these shortcomings (in particular, the ones regarding instantiation of subclasses).
An Example
Suppose you're interested in finding classes that implement an interface com.example.Example
:
package com.example;
public interface Example {
public String getStr();
}
The class com.example.ExampleImpl
implements that interface:
package com.example;
public class ExampleImpl implements Example {
public String getStr() {
return "ExampleImpl's string.";
}
}
You would declare the class ExampleImpl
is an implementation of Example
by creating a file META-INF/services/com.example.Example
containing the text com.example.ExampleImpl
.
Then, you could obtain an instance of each implementation of Example
(including an instance of ExampleImpl
) as follows:
ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
System.out.println(example.getStr());
}
// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.