Reputation: 283
Let's say I have a package that contains all subclasses of 'Device', like this one:
class TV extends Device{
@override
public void run() {
//code goes here
}
private void moreMethods(String args){
// more code
}
}
In the main class it should be possible to instantiate and run every subclass of Device at runtime by calling everyone's 'run' method.
The benefit of this is to allow users to simply put new Device files into the package and said files will run automatically without having to edit the main class.
Is there a clean way to do this? I was thinking that maybe if I had a txt file in that package with every subclass name, it would be possible. Minor inconvenience being the user had to edit the file and add the new device's name.
Is there another way to achieve the same result? I'm interested in providing a way for users to add new device files without editing existent code.
Upvotes: 4
Views: 11498
Reputation: 32831
Another method would consist in using a list of class in a text file (say META-INF/device-classes.txt), in each jar containing device classes. You can then list the URLs of each device-classes.txt file as follows:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Enumeration<URL> enm = cl.findResources("META-INF/device-classes.txt");
while (enm.hasMoreElements()) {
runAllClassesIn(enm.nextElement());
}
Upvotes: 0
Reputation: 109613
There is the Java Service Provider Interface. That is a mechanism where - given an interface one can find all instances that implement this interface. Requirement for an implementation provider that its jar contains a file in META-INF subdirectory with as name the interface: java.lang.Runnable
and as content one or more full class names.
This is more or less what you proposed with a .txt file. Only you can use separate jars.
You could require that all provided classes come in their jar with the SPI specification. Place the jars in a subdirectory plugins
and have that path as Class-Path in the MANIFEST.MF of your own jar. As you know in a class path besides jars also directories are allowed.
More tricking is the alternative using the Reflections library. I find it a kind of abuse, but it offers just what you want, with some efficiency cost.
Alternatively you could require that the classes should be annotated, so you can scan all class annotations. A bit of coding effort required.
Upvotes: 1
Reputation:
Why do you wan't to use Reflection it is heavy cost to do so and could be error prone. You can use a DeviceProvider where the client can register a Device. He can fetch the Device by it's Class Object or it's canonical name. Then He can downcast to access custom method or deal with the Device instance and call common methods.
From Effective Java:
// Service interface
public interface Service {
... // Service-specific methods go here
}
// Service provider interface
public interface Provider {
Service newService();
}
// Noninstantiable class for service registration and access
public class Services {
private Services() { } // Prevents instantiation (Item 4)
// Maps service names to services
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
// Provider registration API
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}
// Service access API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name) {
Provider p = providers.get(name);
if (p == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return p.newService();
}
}
The advantage is that you can provide a default implementation and allow the client to register their custom ones. JDBC DriverManager works quite the same way
Upvotes: 0
Reputation: 48837
If reflection is an option, you could first find all the classes of the dedicated package (Can you find all classes in a package using reflection?, How to get all classes names in a package?), then use:
for (Class<?> candidateClass : foundClasses) {
if (Device.class.isAssignableFrom(candidateClass)) {
candidateClass.newInstance().run();
}
}
Upvotes: 4