You can use the service provider mechanism. This works best when actively using Java’s module system as then, the necessary parts are even integrated into the Java language.
The module containing the Parent
interface must export its package, if implementations in other modules should be possible. Then, a module which wants to look up implementations of the interface must have a
uses yourpackage.Parent;
directive in its module-info. A module providing implementations must have a directive like
provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
in its module-info. It’s possible to use the interface and provide implementations within the same module. This might also be the module containing the declaration of the interface. In that case, it’s even possible to use the mechanism within a single module only and not to export the interface at all.
The compiler will already check that the specified implementation classes are public
, have a public
no-arg constructor, and truly implement the service interface. The implementations do not need to reside in an exported package; it’s possible that the interface is the only way to access an implementation from another module.
Since the compiler pre-checks the required invariants, you don’t need to deal with the issues inherent to Reflection. For example, you don’t need to declare or handle InstantiationException
or IllegalAccessException
.
A simple setup could be
module-info
module ExampleApp {
uses yourpackage.Parent;
provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
}
yourpackage/Parent
package yourpackage;
public interface Parent {
}
yourpackage/TheKey
package yourpackage;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheKey {
String value();
}
yourpackage/ChildA
package yourpackage;
@TheKey("Key1")
public class ChildA implements Parent {
}
yourpackage/ChildB
package yourpackage;
@TheKey("Key2")
public class ChildB implements Parent {
}
independentpackage/UsingTheInterfaces
package independentpackage;
import java.util.ServiceLoader;
import yourpackage.Parent;
import yourpackage.TheKey;
public class UsingTheInterfaces {
public static void main(String[] args) {
Parent p = getChild("Key1");
System.out.println(p);
}
static Parent getChild(String key) {
return ServiceLoader.load(Parent.class).stream()
.filter(p -> {
TheKey keyAnno = p.type().getAnnotation(TheKey.class);
return keyAnno != null && keyAnno.value().equals(key);
})
.findAny()
.map(ServiceLoader.Provider::get)
.orElse(null); // decide how to handle absent keys
}
}
As said, when you have an exports yourpackage;
directive, implementations can be provided by other modules and the ServiceLoader
will discover those implementations dynamically for all modules present at runtime. The using module does not need to know them at compile time.
There is a predecessor mechanism for older Java versions not having a module system, which is also handled by ServiceLoader
in a backwards compatible manner. Those providers have to be packaged in a jar file containing a file META-INF/services/yourpackage.Parent
containing a list of all classes within the jar file implementing the interface. Before Java 9, the ServiceLoader
also lacks the Stream API support that allowed to query the annotations before instantiating the implementation. You can only use the Iterator
that will already instantiate the classes and return an instance.
One way to avoid unnecessary overhead with the old API, is to split the interface into a service provider interface and an actual service interface. The provider interface will supply meta information and act as a factory for actual service implementations. Compare with the relationship between CharsetProvider
and Charset
or FileSystemProvider
and FileSystem
. These examples also demonstrate that the service provider mechanism is already widely used. More examples can be found in the java.base
module documentation.
This was just an overview; the API documentation of ServiceLoader
contains more details about the mechanism.