tutorials.de Buch-Aktion 05/2012
ERLEDIGT
NEIN
ANTWORTEN
4
ZUGRIFFE
578
EMPFEHLEN
  • An Twitter übertragen
  • An Facebook übertragen
AUF DIESES THEMA
ANTWORTEN
  1. #1
    Registriert seit
    Jun 2002
    Ort
    Saarbrücken (Saarland)
    Beiträge
    9.886
    Blog-Einträge
    29
    Hallo,

    hier mal wieder ein kleines einfaches Beispiel für einen flexiblen Erweiterungsmechanismus auf Basis des ServiceLoader APIs (http://download.oracle.com/javase/6/...iceLoader.html).

    Dazu definieren wir uns ein Marker-Interface womit wir "Erweiterungsklassen" in unserer Anwendung markieren könnnen.

    Unser Marker-Interface Extension:
    Code java:
    1
    2
    3
    4
    
    package de.tutorials.extensibility;
     
    public interface Extension {
    }

    Unser Interface ExtensionRegistry:
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    
    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 :
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    
    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:
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    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:
    Code java:
    1
    2
    3
    4
    5
    6
    7
    
    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:
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    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:
    Code java:
    1
    2
    3
    4
    
    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 :
    1
    
    de.tutorials.app.components.StandardComponent

    Lassen wir nun unsere Anwendung laufen, so sehen wir folgende Ausgabe:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    
    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:
    Code java:
    1
    2
    3
    4
    
    package de.tutorials.app.components;
     
    public class Component1A extends AbstractComponent {
    }

    und

    Code java:
    1
    2
    3
    4
    
    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 :
    1
    2
    
    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 :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    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 :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    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-...ml#post1850187
    http://www.tutorials.de/java/358931-...ml#post1859684

    Gruß Tom
    Angehängte Dateien Angehängte Dateien
     
    Java rocks!
    How to become a good Java Programmer?
    Does IT in Java and .Net
    The only valid measurement of code quality: WTFs / minute
    Blog
    Xing
    Twitter

  2. #2
    SE Tutorials.de Gastzugang
    Anmerkung aus Erfahrung:
    Dieses Prinzip lässt sich NICHT in ein externes Jar auslagern. Die gesamte ServiceLoader-Einheit muss zum Startzeitpunkt der VM im CLASSPATH bekannt sein !
     

  3. #3
    Registriert seit
    Jun 2002
    Ort
    Saarbrücken (Saarland)
    Beiträge
    9.886
    Blog-Einträge
    29
    Hallo,

    Dieses Prinzip lässt sich NICHT in ein externes Jar auslagern. Die gesamte ServiceLoader-Einheit muss zum Startzeitpunkt der VM im CLASSPATH bekannt sein !
    Ich denke das stimmt so nicht.... das Interface mit dem du nach Erweiterungen suchst muss natürlich dem Modul bekannt sein, das Erweiterungen dazu sucht...

    Weitere jars kannst du der Applikation auch einfach hinzufügen, du musst nur in der Anwendung erkennen, dass es neue Erweiterungen gibt und einen neuen ClassLoader erzeugen welche dann die neuen Erweiterungen berücksichtigt.

    Gruß Tom
     
    Java rocks!
    How to become a good Java Programmer?
    Does IT in Java and .Net
    The only valid measurement of code quality: WTFs / minute
    Blog
    Xing
    Twitter

  4. #4
    SE Tutorials.de Gastzugang
    Mein Post zielte darauf ab das du immer nur Lösungen und "Tricks" postest an denen man ganz schön rumtricksen muss ... nämlich das manuelle angeben des CP. Genau das ist aber irgendwie nicht der Sinn eines Plug-In basierten Systems da sich der Plug-In-Handler in aller regel seine Plug-In's selbst suchen können sollte. Das wirklich Problem was hierbei auftritt ist wenn man einen solchen Plug-In-Handler ebenfalls als art Plug-In oder Extension der Applikation hinzufügen möchte. Es kracht an genau dem Punkt an dem das gesamte Plug-In System zur Runtime nachgeladen wird und nicht schon zur Laufzeit bekannt ist.

    Hier sollte man wirklich auf diese Besonderheit hinweisen da einige User *unter anderem auch ich* sonst daran verzweifeln das es nicht läuft weil man es als neue Modul seiner Applikation hinzugefügt hat. Es besteht schon ein gewisser Aufwand in einen bestehende Anwendung soetwas im nachhinein zu integrieren.
     

  5. #5
    Registriert seit
    Jun 2002
    Ort
    Saarbrücken (Saarland)
    Beiträge
    9.886
    Blog-Einträge
    29
    Hallo,

    Mein Post zielte darauf ab das du immer nur Lösungen und "Tricks" postest an denen man ganz schön rumtricksen muss ... nämlich das manuelle angeben des CP
    Wo ist denn im obigen Beispiel ein manueller Classpath gesetzt?


    Gruß Tom
     
    Java rocks!
    How to become a good Java Programmer?
    Does IT in Java and .Net
    The only valid measurement of code quality: WTFs / minute
    Blog
    Xing
    Twitter

Ähnliche Themen

  1. Moderner Login-Mechanismus?
    Von Trash im Forum PHP
    Antworten: 3
    Letzter Beitrag: 16.12.10, 11:10
  2. ServiceLoader API
    Von dadom110 im Forum Java
    Antworten: 6
    Letzter Beitrag: 21.09.10, 13:27
  3. Tomcat Login Mechanismus
    Von Thomas Darimont im Forum Enterprise Java (JEE, J2EE, Spring & Co.)
    Antworten: 29
    Letzter Beitrag: 27.05.09, 10:41
  4. Mechanismus für Virtuelle Methodenaufrufe umgehen.
    Von Thomas Darimont im Forum C/C++
    Antworten: 4
    Letzter Beitrag: 31.10.04, 16:45
  5. zahnrad mechanismus
    Von easybrother im Forum 3D Studio Max
    Antworten: 6
    Letzter Beitrag: 24.02.03, 23:31

Stichworte