21.10.2015 yahe code java legacy
Bei größeren Anwendungen, die flexibel in verschiedenen Bereichen einsetzbar sein sollen, kommt schnell der Wunsch auf, diese erweiterbar zu gestalten, sodass andere Nutzer zur Not eigene Erweiterungen schreiben können. Java bietet hierfür mit seinen von Hause aus mitgebrachten Classloadern bereits die passende Grundlage, diese muss nur noch richtig zusammengesetzt werden.
Um das Vorgehen einmal darzustellen, habe ich ein kleines Projekt aufgesetzt, das aus drei Teilen besteht:
Fangen wir mit der Implementierung der Klassenbibliothek "lib" an. Die beschriebenen Schritte sind beispielhaft für die Entwicklungsumgebung Netbeans, sollten in anderen IDEs jedoch genauso einfach umsetzbar sein:
package com.example.loadclass.lib;
public interface ExampleInterface {
public String returnValue();
}
Nun machen wir weiter mit der Implementierung des Plugins:
package com.example.loadclass.plugin;
import com.example.loadclass.lib.ExampleInterface;
public class ExamplePlugin implements ExampleInterface {
@Override
public String returnValue() {
return "Hello world!";
}
}
Bis hierhin gibt es, abgesehen davon, dass wir Interface und Implementierung in eigene Projekte auslagern, wenig Neues im Vergleich zu einer einfachen Java-Anwendung. Spannend wird es in der eigentlichen Anwendung, die wir nun anlegen:
package com.example.loadclass.app;
import com.example.loadclass.lib.ExampleInterface;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public class Main {
protected static final String LOADCLASS_CLASS_EXTENSION = ".class";
protected static final char LOADCLASS_CLASS_SEPARATOR = '.';
protected static final String LOADCLASS_JAR_EXTENSION = ".jar";
protected static final char LOADCLASS_PATH_SEPARATOR = '@';
protected static final String LOADCLASS_URL_PREFIX = "file://";
protected static final String APP_PLUGIN_CLASS = "com.example.loadclass.plugin.ExamplePlugin";
protected static final String APP_PLUGIN_JAR = "/Users/kenny/Desktop/loadClass/plugin/dist/plugin.jar";
protected static final String APP_PLUGIN_PATH = APP_PLUGIN_CLASS + "@" + APP_PLUGIN_JAR;
// structure is "<class>@<path>", where "<path>" may be a folder or a *.jar file
protected static boolean checkName(String aString) {
boolean lResult = false;
if (null != aString) {
// the combinator must not be at the very beginning or at the very end
if ((aString.indexOf(LOADCLASS_PATH_SEPARATOR) > 0) &&
(aString.indexOf(LOADCLASS_PATH_SEPARATOR) < aString.length()-1)) {
String lClassName = aString.substring(0, aString.indexOf(LOADCLASS_PATH_SEPARATOR));
String lPathName = aString.substring(aString.indexOf(LOADCLASS_PATH_SEPARATOR)+1);
File lPath = new File(lPathName);
if (lPath.exists()) {
if (lPath.isFile()) {
if (lPathName.endsWith(LOADCLASS_JAR_EXTENSION)) {
try (JarFile lJarFile = new JarFile(lPath)) {
if (null != lJarFile.getJarEntry(lClassName.replace(LOADCLASS_CLASS_SEPARATOR,
File.separatorChar) +
LOADCLASS_CLASS_EXTENSION)) {
lResult = true;
}
} catch (Exception lException) {}
}
} else {
if (lPath.isDirectory()) {
// fix path delimiter
if (File.separatorChar != lPathName.charAt(lPathName.length()-1)) {
lPathName = lPathName + File.separator;
}
// check if the class exists
lPath = new File(lPathName +
lClassName.replace(LOADCLASS_CLASS_SEPARATOR, File.separatorChar) +
LOADCLASS_CLASS_EXTENSION);
if (lPath.isFile()) {
lResult = true;
}
}
}
}
}
}
return lResult;
}
protected static Class loadClass(String aName) {
Class lResult = null;
if (checkName(aName)) {
// get name parts of parameter
String lClassName = aName.substring(0, aName.indexOf(LOADCLASS_PATH_SEPARATOR));
String lPathName = aName.substring(aName.indexOf(LOADCLASS_PATH_SEPARATOR)+1);
// check that directory name ends with path separator
if (new File(lPathName).isDirectory()) {
if (File.separatorChar != lPathName.charAt(lPathName.length()-1)) {
lPathName = lPathName + File.separator;
}
}
try {
URL lURL = new URL(LOADCLASS_URL_PREFIX + lPathName);
URLClassLoader lClassLoader = new URLClassLoader(new URL[]{lURL},
ClassLoader.getSystemClassLoader());
lResult = lClassLoader.loadClass(lClassName);
} catch (Exception lException) {}
}
return lResult;
}
protected static Object createObject(String aName, Class aInterface) {
Object lResult = null;
if (null != aInterface) {
Class lClass = loadClass(aName);
if (null != lClass) {
boolean lCorrectInterface = false;
for (Class lInterface : lClass.getInterfaces()) {
if (aInterface.equals(lInterface)) {
lCorrectInterface = true;
break;
}
}
if (lCorrectInterface) {
try {
lResult = lClass.newInstance();
} catch (Exception lException) {}
}
}
}
return lResult;
}
public static void main(String[] args) {
ExampleInterface lObject = (ExampleInterface)createObject(APP_PLUGIN_PATH,
ExampleInterface.class);
if (null != lObject) {
System.out.println(lObject.returnValue());
}
}
}
Wenn ihr die Pakete "lib.jar", "plugin.jar" und "app.jar" alle einmal erzeugt habt, müsst ihr euch ansehen, unter welchem Dateipfad das Plugin-Paket erreichbar ist. In meinem Beispiel ist es "/Users/kenny/Desktop/loadClass/plugin/dist/plugin.jar". Diesen vollständigen Pfad müsst ihr in die Variable "APP_PLUGIN_JAR" schreiben. In einem tatsächlichen Anwendungsfall würde man solche Informationen dann wahrscheinlich eher aus einer Konfigurationsdatei auslesen.