Well I will post a little example that I use. It isn’t quite what you have in mind but could easily be used for that purpose (in fact I have on a few occasions).
/**
*
* @author Quew8
* @param <T>
*/
public class ServiceImplLoader<T extends ServiceImpl> {
private final ServiceLoader<T> loader;
private final T[] loadedImpls;
private final Class<T> servClazz;
public ServiceImplLoader(Class<T> servClazz, ClassLoader classLoader, T... loadedImpls) {
this.servClazz = servClazz;
this.loadedImpls = loadedImpls;
this.loader = ServiceLoader.load(servClazz, classLoader);
}
public ServiceImplLoader(Class<T> servClazz, URL[] urls, T... loadedImpls) {
this(servClazz, new URLClassLoader(urls), loadedImpls);
}
public ServiceImplLoader(Class<T> servClazz, T... loadedImpls) {
this(servClazz, (ClassLoader) null, loadedImpls);
}
public T getImplementation() throws NoSuitableImplementationException {
T topImpl = null;
ArrayList<T> allImpls = getAllImplementations();
for(T t: allImpls) {
if(
t.isApplicable() &&
(
(topImpl == null || topImpl.getPrecedence() == -1 ) ||
(t.getPrecedence() < topImpl.getPrecedence() && t.getPrecedence() != -1)
)
) {
topImpl = t;
}
}
if(topImpl != null) {
return topImpl;
}
throw new NoSuitableImplementationException(servClazz, loader);
}
public T getImplementationNoThrow() {
try {
return getImplementation();
} catch(NoSuitableImplementationException ex) {
throw new RuntimeException(ex);
}
}
public T[] getAllImplementations(T[] dest) {
return getAllImplementations().toArray(dest);
}
public ArrayList<T> getAllImplementations() {
ArrayList<T> impls = new ArrayList<T>();
for(T t: loader) {
if(t.isApplicable()) {
impls.add(t);
}
}
for(T t: loadedImpls) {
if(t.isApplicable()) {
impls.add(t);
}
}
return impls;
}
}
/**
*
* @author Quew8
*/
public interface ServiceImpl {
public boolean isApplicable();
public int getPrecedence();
}
/**
*
* @author Quew8
*/
public class NoSuitableImplementationException extends Exception {
public <T extends ServiceImpl> NoSuitableImplementationException(Class<T> servClazz, Iterable<T> impls) {
super(getMsg(servClazz, impls));
}
private static <T extends ServiceImpl> String getMsg(Class<T> servClazz, Iterable<T> impls) {
String s = "No suitable implementation found for " + servClazz.getName() + "\nFound Implementations:";
for(T t: impls) {
s += "\n " + t.getClass().getName() + "\n Applicable: " + t.isApplicable() + "\n Precedence: " + t.getPrecedence();
}
return s;
}
}
So the ServiceImpl loader finds the most appropriate ServiceImpl in it’s ClassLoader. When you want to make your own service, you create an interface extending ServiceImpl then all of the implementations implement this interface. The precedence thing is slightly stupid here. Essentially the lower the value returned by getPrecedence() in the ServiceImpl the more preferable the implementation is unless it returns -1 which I use for things like no implementations - which you’ll be wanting and I’ll explain later. The URL[] based ServiceImplLoader constructor wants a list of URLs pointing to .jar files however be warned they have to me registered as Services in their manifest files as described here: http://docs.oracle.com/javase/tutorial/ext/basics/spi.html#package-in-jar-files.
Now I consider a no implementation implementation to be an implementation that merely throws UnsupportedOperationException whenever you call one of its methods (other than getPrecedence() and is isApplicable() which should return -1 and true respectively). Generally I have these built into the core application and input them into the ServiceLoader with the list of “loadedImpls” in the constructor. That way even if none of the other Implementations are applicable you can fall back on this and the program will run as long as you don’t use the Service.
I think this is what you are looking for? Please feel free to ask if you have any questions about the code or how to use it.
@HeroesGraveDev It’s not too messy as long as you just use Java’s own class loaders and don’t try implementing your own - here the URLClassLoader really comes in useful. It’s probably easy enough to do but I steered clear of it.