8

I want to make an application that can dynamically load plug-ins but I've not found any literature on Internet.

The difficult thing is: I don't know the name in advance.

For example I've a Plugin interface:

public interface Plugin {
    public static Plugin newPlugin();
    public void executePlugin(String args[]);
}

So that every Class implementing Plugin in the jar file are instantiated in a list:

Method method = classToLoad.getMethod ("newPlugin");
mylist.add(method.invoke(null);
  1. First problem is, I cannot have a static method in an interface.
  2. Second problem is, I don't know how to find all classes that implement an interface

Thanks for your help.

turbanoff
  • 2,439
  • 6
  • 42
  • 99
FloFu
  • 609
  • 1
  • 5
  • 21
  • Is having a config file specifying which plugins to load a suitable solution to your problem? – Mac Dec 13 '12 at 23:47
  • Hi Mac, in my case I would like to avoid having a configuration file, just if a jar is present in a folder try to load it. Thanks. – FloFu Dec 14 '12 at 00:35

2 Answers2

27

So it seems like you want to dynamically discover Classes that implement a specific interface (e.g., Plugin) at runtime. You have basically two choices for this:

  1. Use a component framework like osgi
  2. Use Java's internal discovery process (ServiceLoader)

Since there are many good tutorials on osgi (also small ones), I will not detail that here. To use Java's internal discovery process, you need to do the following:

  • Bundle all "new" classes that you wish to discover into a jar file
  • Create a new file inside the jar file: META-INF/services/package.Plugin You must use the full package qualifier here
  • This file is a simple text file and contains the fully qualified name of each class implementing Plugin in that jar-file
  • Place that jar file into the classpath of your (potentially already running) application
  • Discover the services:

Service discovery is done like this:

ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class)
for (Plugin p : loader) {
    // do something with the plugin
}

There are more details here: http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html

As for static methods in interfaces: not possible. The semantics of that would also be somewhat weird as static methods are accessible without an instance of a class, and interfaces just define the methods, without any functionality. Thus, static would allow to call Interface.doSomething() whereas the interface does not define any functionality, this leads just to confusion.

edit:

added description what should be in the meta-file

senyor
  • 332
  • 4
  • 9
SirRichie
  • 1,176
  • 9
  • 14
  • 1
    I don't think anybody said welcome to you SirRichie, but you are very welcome to this community. Voted up all four of your answers :) – Maarten Bodewes Dec 13 '12 at 23:49
  • Normally you just indent the first line with 4 spaces, after you skipped a line. Then after another empty line you start at position 0 for the rest of your normal text. You can surround code with `` characters for inline code. – Maarten Bodewes Dec 13 '12 at 23:53
  • Yeah... I did that. Now I found out that this does not work, if the code immediately follows a list item. So I put some filler text there and it works like a charm now. – SirRichie Dec 13 '12 at 23:55
  • While an interface can't have a static method, an abstract class can, and abstract classes work with SPI jars just as interfaces do. The (pleasantly short) SPI jar specification is [here](http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Service_Provider). – VGR Dec 14 '12 at 12:19
  • Actual URL of the ServiceLoader class is http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html - the one in the text refers to the whole Java API ;) – BxlSofty Dec 03 '15 at 08:41
2

Regarding your first problem, not being able to have static methods in the interface, my suggestion is to simply use the interface has a marker and instantiate it.

You're plugin interface can simply be:

public interface Plugin {
  public void executePlugin(String args[]);
}

And then you can do:

if (someClass instanceOf Plugin) {
  mylist.add(someClass.newInstance());
}

This leads to the second question, how will you get the someClass reference. There is no standard way to find all classes implementing a given interface in your classpath, although, an approach you can do is scan the jars in your classpath, if a given file ends with .class determine it's full qualified name through it's path inside the jar and use Class.forName() method to materialize the Class.

In pseudo code something like this:

for each jar in your classpath {
  for each file in JarFile {
    if (file ends with .class) {
       materialize class using Class.forName
     } 
  }
}

With the Class instance you can check if it's implementing your Plugin interface.

Also keep in mind that if you need to add any context to your plugins you can create a constructor in every plugin that receives your context object, instead of having the default constructor. In such case instead of using newInstance() you would have to get the constructor with arguments you wanted via reflection.

pabrantes
  • 2,161
  • 1
  • 28
  • 34