Welche guten Möglichkeiten gibt es in Java, APIs von der Implementierung * ganzer Projekte * zu trennen?

8

Stellen Sie sich vor, Sie haben ein Softwaremodul, das ein Plugin für ein Programm ist (ähnlich wie Eclipse), und Sie möchten, dass es eine API hat, die andere Plugins aufrufen können. Ihr Plugin ist nicht frei verfügbar, so dass Sie eine separate API - Modul haben wollen, das ist frei verfügbar und ist das einzige , was andere Plugins müssen direkt Link zu - API - Clients nur mit dem API - Modul zusammenstellen können, und nicht das Implementierungsmodul, auf dem Erstellungspfad. Wenn die API auf kompatible Weise weiterentwickelt werden muss, können Client-Plugins das API-Modul sogar in ihre eigenen Jars aufnehmen (um zu verhindern, dass Errors auf den Zugriff auf nicht vorhandene Klassen zurückzuführen ist).

Die Lizenzierung ist nicht der einzige Grund, API und Implementierung in separate Module zu integrieren. Es kann sein, dass das Implementierungsmodul komplex ist und unzählige eigene Abhängigkeiten aufweist. Eclipse-Plugins haben normalerweise interne und nicht interne Pakete, wobei die nicht internen Pakete einem API-Modul ähneln (beide sind im selben Modul enthalten, können jedoch getrennt werden).

Ich habe dafür verschiedene Alternativen gesehen:

  1. Die API befindet sich in einem von der Implementierung getrennten Paket (oder einer Gruppe von Paketen). Die API-Klassen rufen direkt Implementierungsklassen auf. Die API kann ohne die Implementierung nicht aus dem Quellcode kompiliert werden (was in einigen seltenen Fällen wünschenswert ist). Es ist nicht einfach, die genauen Auswirkungen des Aufrufs von API-Methoden vorherzusagen, wenn die Implementierung nicht installiert ist. Daher vermeiden Clients dies normalerweise.

    package com.pluginx.api;
    import com.pluginx.internal.FooFactory;
    public class PluginXAPI {
        public static Foo getFoo() {
            return FooFactory.getFoo();
        }
    }
  2. Die API befindet sich in einem separaten Paket und verwendet Reflection, um auf die Implementierungsklassen zuzugreifen. Die API kann ohne Implementierung kompiliert werden. Die Verwendung von Reflection kann zu Leistungseinbußen führen (Reflection-Objekte können jedoch zwischengespeichert werden, wenn es sich um ein Problem handelt. Es ist einfach zu steuern, was passiert, wenn die Implementierung nicht verfügbar ist.

    package com.pluginx.api;
    public class PluginXAPI {
        public static Foo getFoo() {
            try {
                return (Foo)Class.forName("com.pluginx.internal.FooFactory").getMethod("getFoo").invoke(null);
            } catch(ReflectiveOperationException e) {
                return null;
                // or throw a RuntimeException, or add logging, or raise a fatal error in some global error handling system, etc
            }
        }
    }
  3. Die API besteht nur aus Schnittstellen und abstrakten Klassen sowie einer Möglichkeit, eine Instanz einer Klasse abzurufen.

    package com.pluginx.api;
    public abstract class PluginXAPI {
        public abstract Foo getFoo();
    
        private static PluginXAPI instance;
        public static PluginXAPI getInstance() {return instance;}
        public static void setInstance(PluginXAPI newInstance) {
            if(instance != null)
                throw new IllegalStateException("instance already set");
            else
                instance = newInstance;
        }
    }
  4. Das gleiche wie oben, aber der Client-Code muss die ursprüngliche Referenz von einer anderen Stelle erhalten:

    // API
    package com.pluginx.api;
    public interface PluginXAPI {
        Foo getFoo();
    }
    
    // Implementation
    package com.pluginx.internal;
    public class PluginX extends Plugin implements PluginXAPI {
        @Override
        public Foo getFoo() { ... }
    }
    
    // Client code uses it like this
    PluginXAPI xapi = (PluginXAPI)PluginManager.getPlugin("com.pluginx");
    Foo foo = xapi.getFoo();
  5. Tu es nicht. Stellen Sie sicher, dass Clients direkt mit dem Plugin verknüpft sind (verhindern Sie jedoch, dass sie Nicht-API-Methoden aufrufen). Dies würde es für viele andere Plugins (und die meisten Open Source-Plugins) schwierig machen, die API dieses Plugins zu verwenden, ohne einen eigenen Wrapper zu schreiben.

user253751
quelle

Antworten:

3

Die Antwort auf Ihre Frage hängt von der Definition von "gut" in der Frage "Was sind einige gute Möglichkeiten, um APIs von der Implementierung von ... zu trennen?" Ab.

Wenn "gut" für Sie als Hersteller der API "pragmatisch einfach zu implementieren" bedeutet, kann dies hilfreich sein:

Da Sie Java verwenden, wo JAR-Bibliotheken zur Laufzeit geladen werden, würde ich eine etwas andere Alternative vorschlagen:

  • Die API besteht nur aus Schnittstellen plus einer Dummy-Implementierung dieser Schnittstellen, die keine echte Funktion hat.

Der Kunde, der Ihre API verwendet, kann gegen dieses Dummy-Jar kompilieren und zur Laufzeit können Sie das Dummy-Jar durch das lizenzierte ersetzen.

Ähnliches geschieht mit slf4j, wo die tatsächliche Protokollierungsimplementierung, die mit Ihrer Anwendung verwendet werden soll, durch Ersetzen des Protokollierungs-JAR ausgewählt wird

k3b
quelle
So erweitern Sie das Beispiel slf4j : slf4j enthält Dummy-Implementierungen in den Quellen, die nach dem Kompilieren entfernt werden. Der Effekt davon ist eine starke Referenz (die zu einem ClassDefNotFound führt, wenn in Ihrer Anwendung keine Implementierung bereitgestellt wird)
dot_Sp0T
@ dot_Sp0T Ich stimme zu, dass das Problem "ClassDefNotFound" für Android besteht, da in Android alle Jars in einer APK-Datei zusammengeführt werden. In gewöhnlichem Java (ohne Verwendung eines Obfuscators) sollte dies kein Problem sein. Wenn ich falsch liege, weiß ich, unter welchen Umständen dies geschieht.
k3b
4

Haben Sie sich die ServiceLoader- Mechanismen in Java angesehen? Grundsätzlich können Sie die Implementierung einer Schnittstelle über die Manifestdatei in einem JAR angeben. Oracle bietet auch einige weitere Informationen zu Plugins in Java-Programmen.

Benni
quelle
1

Soweit ich weiß, verwenden die Leute oft das Fabrikmuster dafür.

Sie fügen die API-Schnittstellen in ein separates Modul ein (z. B. eine JAR-Datei). Wenn die Clients die API verwenden möchten und Zugriff auf eine Implementierung der API haben, verfügt die Implementierung der API über einen werkseitigen Einstiegspunkt für die Clients Erstellen Sie konkrete Objekte, die diese API implementieren.

Dies bedeutet, dass Ihre erste Idee von oben die größte Ähnlichkeit aufweist.

InformiertA
quelle