Thomas Darimont
Erfahrenes Mitglied
Hallo,
hier mal wieder ein kleines einfaches Beispiel für einen flexiblen Erweiterungsmechanismus auf Basis des ServiceLoader APIs (http://download.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html).
Dazu definieren wir uns ein Marker-Interface womit wir "Erweiterungsklassen" in unserer Anwendung markieren könnnen.
Unser Marker-Interface Extension:
Unser Interface ExtensionRegistry:
Hier unsere Standard ExtensionRegistry Implementierung ServiceLoaderExtensionRegistry :
Unsere Anwendung:
In unserer Anwendung kann man Komponenten hinzufügen welche "gestartet" werden können.
Deshalb definieren wir ein interface Component das von unserem Extension-Marker Interface erbt:
Für das Beispiel definieren wir noch eine Abstrakte Klasse AbstractComponent
damit wir schnell viele kleine Component Implementierungen definieren können, die beim start() ihren Namen ausgeben:
Zusätzlich definieren wir eine StandardComponent, schließlich muss die Anwendung ja auch ein wenig Standard-Funktionalität mitbringen:
Damit diese Standard Implementierung vom ServiceLoader gefunden werden kann, müssen wir die Verzeichnisse META-INF/services/ im Classpath Root (im Eclipse zbsp. im src Folder) definieren. Dort erzeugen wir eine Datei mit dem Namen des Interfaces "de.tutorials.app.components.Component" (das ist die Konvention des ServiceLoaders).
In diese Datei schreiben wir nun den voll qualifizierten Klassennamen (fqcn) unserer Standardimplementierung:
Lassen wir nun unsere Anwendung laufen, so sehen wir folgende Ausgabe:
Wie man sieht wurde unsere StandardComponent gefunden, jedoch keine zusätztlichen Erweiterungen. Das ist auch richtig so, denn wir haben ja noch keine definiert.
Wir erstellen nun eine solche Erweiterung. Dazu erzeugen wir ein neues java-Projekt: de.tutorials.training.app.extension1 und erstellen dort folgende zwei Klassen:
und
wieder erzeugen wir eine Datei: /de.tutorials.training.app.extension1/src/META-INF/services/de.tutorials.app.components.Component ; nun mit folgendem Inhalt:
Diesmal möchten wir über unseren Erweiterungsmechanismus gleich zwei Component Implementierungen bereitstellen.
Nun erzeugen wir für unsere Anwendung (app.ext.jar als runnable jar!) und das Erweiterungs Projekt (extension1.ext.jar) jeweils ein jar.
Das das app.jar welches unsere Hauptanwendung enthält erzeugen wir zum test in ein Verzeichnis c:\temp\app
Dazu erstellen wir nun ein Verzeichnis c:\temp\app\ext. Dieses Verzeichnis wird unsere Erweiterungs-jars aufnehmen.
Ich habe die Erweiterungs-Jars durch die Endung .ext.jar explizit als solche gekennzeichnet.
Starten wir nun unsere Anwendung über die Konsole via:
java -jar app.jar
... so sehen wir folgende Ausgabe:
Wie wir sehen hat unsere Erweiterungsmechanismus nun auch die Erweiterungen aus unserem extension1.ext.jar erkannt (Component1A, Component1B).
Hier die Ausgabe mit einem weiteren extension-jar:
Ich hoffe dieses Beispiel hat klar gemacht wie einfach man sehr flexible Erweiterungsmechanismen auf Basis des ServiceLoader APIs definieren kann.
Hier ein paar weitere Beispiele zum ServiceLoader API:
http://www.tutorials.de/java/357126-wieder-mal-java-und-plug-ins.html#post1850187
http://www.tutorials.de/java/358931-services-dynamisch-laden.html#post1859684
Gruß Tom
hier mal wieder ein kleines einfaches Beispiel für einen flexiblen Erweiterungsmechanismus auf Basis des ServiceLoader APIs (http://download.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html).
Dazu definieren wir uns ein Marker-Interface womit wir "Erweiterungsklassen" in unserer Anwendung markieren könnnen.
Unser Marker-Interface Extension:
Java:
package de.tutorials.extensibility;
public interface Extension {
}
Unser Interface ExtensionRegistry:
Java:
package de.tutorials.extensibility;
import java.util.List;
public interface ExtensionRegistry {
<TExtension extends Extension> List<TExtension> getExtensions(Class<TExtension> extensionClass);
}
Hier unsere Standard ExtensionRegistry Implementierung ServiceLoaderExtensionRegistry :
Java:
package de.tutorials.extensibility;
import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
public class ServiceLoaderExtensionRegistry implements ExtensionRegistry{
private final static Logger logger = Logger.getLogger(ServiceLoaderExtensionRegistry.class.getName());
private ClassLoader extensionClassLoader;
private AtomicBoolean initialized = new AtomicBoolean();
protected String extensionLookupPath = System.getProperty("de.tutorials.extensibility.extension.lookupPath", "./ext/");
protected String extensionFileExtension = ".ext.jar";
protected FilenameFilter extensionJarFilter = createExtensionJarFilter();
public void init() {
this.extensionClassLoader = createExtensionClassLoader(lookupExtensionUrls());
this.initialized.set(true);
}
@Override
public <TExtension extends Extension> List<TExtension> getExtensions(Class<TExtension> extensionClass){
if(!this.initialized.get()){
init();
}
ServiceLoader<TExtension> extensionLoader = ServiceLoader.load(extensionClass, getExtensionClassLoader());
List<TExtension> extensions = new ArrayList<TExtension>();
for(Iterator<TExtension> iter = extensionLoader.iterator();iter.hasNext();){
TExtension extension = iter.next();
logger.info(String.format("Found extension for %s: %s from URL: %s",extensionClass.getName(), extension, extension.getClass().getProtectionDomain().getCodeSource().getLocation()));
extensions.add(extension);
}
if(extensions.isEmpty()){
logger.info(String.format("No extensions were found for %s", extensionClass.getName()));
}
return extensions;
}
private URLClassLoader createExtensionClassLoader(List<URL> extensionJars) {
return new URLClassLoader(extensionJars.toArray(new URL[extensionJars.size()]));
}
private List<URL> lookupExtensionUrls() {
File extensionsFolder = new File(getExtensionLookupPath());
logger.info("Using extension folder: " + extensionsFolder.getAbsolutePath());
List<URL> extensionUrlList = new ArrayList<URL>();
File[] extensionJars = extensionsFolder.listFiles(getExtensionJarFilter());
if(extensionJars == null || extensionJars.length == 0){
logger.info("Found 0 extension jars");
}else{
logger.info(String.format("Found %s extensions",extensionJars.length));
for(File extensionJar : extensionJars){
try {
URL extensionJarUrl = extensionJar.toURI().toURL();
logger.info("Found extension jar: " + extensionJarUrl);
extensionUrlList.add(extensionJarUrl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
return extensionUrlList;
}
protected FilenameFilter createExtensionJarFilter() {
return new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(extensionFileExtension);
}
};
}
public String getExtensionLookupPath() {
return extensionLookupPath;
}
public void setExtensionLookupPath(String extensionLookupPath) {
this.extensionLookupPath = extensionLookupPath;
}
public String getExtensionFileExtension() {
return extensionFileExtension;
}
public void setExtensionFileExtension(String extensionFileExtension) {
this.extensionFileExtension = extensionFileExtension;
}
public FilenameFilter getExtensionJarFilter() {
return extensionJarFilter;
}
public void setExtensionJarFilter(FilenameFilter extensionJarFilter) {
this.extensionJarFilter = extensionJarFilter;
}
public ClassLoader getExtensionClassLoader() {
return extensionClassLoader;
}
public void setExtensionClassLoader(ClassLoader extensionClassLoader) {
this.extensionClassLoader = extensionClassLoader;
}
}
Unsere Anwendung:
Java:
package de.tutorials.app;
import de.tutorials.app.components.Component;
import de.tutorials.extensibility.ExtensionRegistry;
import de.tutorials.extensibility.ServiceLoaderExtensionRegistry;
public class Application {
protected ExtensionRegistry extensionRegistry;
public static void main(String[] args) {
new Application().boot();
}
protected void boot() {
init();
start();
}
protected void init() {
setExtensionRegistry(new ServiceLoaderExtensionRegistry());
}
protected void start() {
for (Component component : getExtensionRegistry().getExtensions(Component.class)) {
component.start();
}
}
public ExtensionRegistry getExtensionRegistry() {
return extensionRegistry;
}
public void setExtensionRegistry(ExtensionRegistry extensionRegistry) {
this.extensionRegistry = extensionRegistry;
}
}
In unserer Anwendung kann man Komponenten hinzufügen welche "gestartet" werden können.
Deshalb definieren wir ein interface Component das von unserem Extension-Marker Interface erbt:
Java:
package de.tutorials.app.components;
import de.tutorials.extensibility.Extension;
public interface Component extends Extension{
void start();
}
Für das Beispiel definieren wir noch eine Abstrakte Klasse AbstractComponent
damit wir schnell viele kleine Component Implementierungen definieren können, die beim start() ihren Namen ausgeben:
Java:
package de.tutorials.app.components;
import java.util.logging.Logger;
public abstract class AbstractComponent implements Component {
@Override
public void start() {
Logger.getLogger(getClass().getName()).info(
"Started " + getClass().getSimpleName());
}
}
Zusätzlich definieren wir eine StandardComponent, schließlich muss die Anwendung ja auch ein wenig Standard-Funktionalität mitbringen:
Java:
package de.tutorials.app.components;
public class StandardComponent extends AbstractComponent {
}
Damit diese Standard Implementierung vom ServiceLoader gefunden werden kann, müssen wir die Verzeichnisse META-INF/services/ im Classpath Root (im Eclipse zbsp. im src Folder) definieren. Dort erzeugen wir eine Datei mit dem Namen des Interfaces "de.tutorials.app.components.Component" (das ist die Konvention des ServiceLoaders).
In diese Datei schreiben wir nun den voll qualifizierten Klassennamen (fqcn) unserer Standardimplementierung:
Code:
de.tutorials.app.components.StandardComponent
Lassen wir nun unsere Anwendung laufen, so sehen wir folgende Ausgabe:
Code:
29.06.2011 00:08:19 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Using extension folder: C:\development\java\workspaces\2.6.1\de.tutorials.training.app\.\ext
29.06.2011 00:08:19 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found 0 extension jars
29.06.2011 00:08:19 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.StandardComponent@b61d36b from URL: file:/C:/development/java/workspaces/2.6.1/de.tutorials.training.app/bin/
29.06.2011 00:08:19 de.tutorials.app.components.AbstractComponent start
INFO: Started StandardComponent
Wie man sieht wurde unsere StandardComponent gefunden, jedoch keine zusätztlichen Erweiterungen. Das ist auch richtig so, denn wir haben ja noch keine definiert.
Wir erstellen nun eine solche Erweiterung. Dazu erzeugen wir ein neues java-Projekt: de.tutorials.training.app.extension1 und erstellen dort folgende zwei Klassen:
Java:
package de.tutorials.app.components;
public class Component1A extends AbstractComponent {
}
und
Java:
package de.tutorials.app.components;
public class Component1B extends AbstractComponent {
}
wieder erzeugen wir eine Datei: /de.tutorials.training.app.extension1/src/META-INF/services/de.tutorials.app.components.Component ; nun mit folgendem Inhalt:
Code:
de.tutorials.app.components.Component1A
de.tutorials.app.components.Component1B
Diesmal möchten wir über unseren Erweiterungsmechanismus gleich zwei Component Implementierungen bereitstellen.
Nun erzeugen wir für unsere Anwendung (app.ext.jar als runnable jar!) und das Erweiterungs Projekt (extension1.ext.jar) jeweils ein jar.
Das das app.jar welches unsere Hauptanwendung enthält erzeugen wir zum test in ein Verzeichnis c:\temp\app
Dazu erstellen wir nun ein Verzeichnis c:\temp\app\ext. Dieses Verzeichnis wird unsere Erweiterungs-jars aufnehmen.
Ich habe die Erweiterungs-Jars durch die Endung .ext.jar explizit als solche gekennzeichnet.
Starten wir nun unsere Anwendung über die Konsole via:
java -jar app.jar
... so sehen wir folgende Ausgabe:
Code:
C:\temp\app>java -jar app.jar
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Using extension folder: C:\temp\app\.\ext
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found 1 extensions
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found extension jar: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.StandardComponent@38503429 from URL: file:/C:/temp/app/app.jar
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.Component1A@6bdd46f7 from URL: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:16:04 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.Component1B@7e0df503 from URL: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:16:04 de.tutorials.app.components.AbstractComponent start
INFO: Started StandardComponent
29.06.2011 00:16:04 de.tutorials.app.components.AbstractComponent start
INFO: Started Component1A
29.06.2011 00:16:04 de.tutorials.app.components.AbstractComponent start
INFO: Started Component1B
Wie wir sehen hat unsere Erweiterungsmechanismus nun auch die Erweiterungen aus unserem extension1.ext.jar erkannt (Component1A, Component1B).
Hier die Ausgabe mit einem weiteren extension-jar:
Code:
C:\temp\app>java -jar app.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Using extension folder: C:\temp\app\.\ext
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found 2 extensions
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found extension jar: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry lookupExtensionUrls
INFO: Found extension jar: file:/C:/temp/app/./ext/extension2.ext.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.StandardComponent@b815859 from URL: file:/C:/temp/app/app.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.Component1A@6100ab23 from URL: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.Component1B@446b7920 from URL: file:/C:/temp/app/./ext/extension1.ext.jar
29.06.2011 00:19:59 de.tutorials.extensibility.ServiceLoaderExtensionRegistry getExtensions
INFO: Found extension for de.tutorials.app.components.Component: de.tutorials.app.components.Component2XXX@65bd0dd4 from URL: file:/C:/temp/app/./ext/extension2.ext.jar
29.06.2011 00:19:59 de.tutorials.app.components.AbstractComponent start
INFO: Started StandardComponent
29.06.2011 00:19:59 de.tutorials.app.components.AbstractComponent start
INFO: Started Component1A
29.06.2011 00:19:59 de.tutorials.app.components.AbstractComponent start
INFO: Started Component1B
29.06.2011 00:19:59 de.tutorials.app.components.AbstractComponent start
INFO: Started Component2XXX
Ich hoffe dieses Beispiel hat klar gemacht wie einfach man sehr flexible Erweiterungsmechanismen auf Basis des ServiceLoader APIs definieren kann.
Hier ein paar weitere Beispiele zum ServiceLoader API:
http://www.tutorials.de/java/357126-wieder-mal-java-und-plug-ins.html#post1850187
http://www.tutorials.de/java/358931-services-dynamisch-laden.html#post1859684
Gruß Tom