Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically loading classes which adhere to an interface

I have several classes which implement two interfaces. All of them implement the BaseInterface and some other interface which is specific to them.

I want to be able to use the loadClass method below to instantiate classes which are referred to in a .properties file and call the common method they all contain (because they implement BaseInterface).

public interface BaseInterface {
    public void doBase();
}

public interface SpecificInterface extends BaseInterface {
    public void doSpecific();
}

public class SpecificClass implements SpecificInterface {
    public void doBase() { ... }

    public void doSpecific() { ... }
}

public class LoadClass() {
    private PropertiesLoader propertiesLoader = new PropertiesLoader();

    public <C extends BaseInterface> C loadClass(String propertyName) {
        Class<C> theClass;

        // Load the class.
        theClass = propertiesLoader.getPropertyAsClass(propertyName);

        // Create an instance of the class.
        C theInstance = theClass.newInstance();

        // Call the common method.
        theInstance.doBase();

        return theInstance;
    }
}

Unfortunately, when I run the code:

loadClassInstance.loadClass("SpecificClass");

I get the following exception:

Exception in thread "main" java.lang.ClassCastException:
SpecificClass cannot be cast to BaseInterface
at LoadClass.loadClass

Any ideas how I would solve this issue?

Many Thanks, Danny

like image 240
Danny Avatar asked Oct 04 '12 14:10

Danny


1 Answers

Java's Service Provider Interface (SPI) libraries allow you to load classes with public parameterless constructors dynamically based on the interfaces they implement, and it's all done through the use of META-INF/services.

First, you'll need the interface:

package com.example;

public interface SomeService {

    String getServiceId();

    String getDisplayName();
}

Then when you need them, you can load them using Java's ServiceLoader class, which implements Iterable:

ServiceLoader<SomeService> loader = ServiceLoader.load(SomeService.class);
for (SomeService serv : loader) {
    System.out.println(serv.getDisplayName());
}

Then when you have 1 or more implementing classes on your classpath, they register themselves in META-INF/services. So if you have the implementation:

package com.acme;

public class SomeImplementation implements SomeService {

    // ...

    public SomeImplementation() { ... }

    // ...
}

Note that this class needs a default no-args constructor, this is not optional.

You register it with the class loader by creating a file in META-INF/services in your classpath (such as in the root of your jar) with the following properties:

  1. The name of the file is the fully qualified class name of the interface, in this case, it's com.example.SomeService
  2. The file contains a newline-separated list of implementations, so for the example implementation, it would contain one line: com.acme.SomeImplementation.

And there you go, that's it. How you build your project will determine where you put the META-INF/services stuff. Maven, Ant, etc. all have ways of handling this. I recommend asking another question about your specific build process if you have any trouble adding these files to your build.

like image 194
Brian Avatar answered Nov 15 '22 16:11

Brian