依赖(插件)JAR 中接口的实现注入?

huangapple go评论62阅读模式
英文:

Inject implementation of interface from dependent (plugin) JAR?

问题

我有一个接口A,其中包含一个方法Result doAction(Param param)。我有一个Spring应用程序,将使用接口的实现并调用其上的doAction()方法。

但是该应用程序本身不定义实现。想法是其他人可以提供他们自己的接口实现的JAR包(插件),主应用程序将将这些作为依赖项导入并调用JAR包的实现上的doAction()方法。

你知道如何在实际中实现这一点吗?我考虑过的一些想法包括:

  • 尝试通过Spring Boot自动装配实现,但这需要我知道其包名并将其添加到组件扫描中。这意味着我需要对“插件” JAR包的命名提出要求,这不是我希望的做法。

  • 使用纯Java的第一个想法是维护一个实现的注册表(例如Set<Interface A>),但插件无法访问该注册表,这将导致依赖关系循环。

目前我正在做的是定义一个"插件"需要实现的REST API,将插件部署在相同的环境中,主应用程序只需通过REST API进行调用。
但出于性能原因,我正在寻找一种不涉及网络通信的直接调用的解决方案。有什么建议吗?

英文:

I have an interface A with a method Result doAction(Param param). I have a Spring application that will use implementations of the interface and call doAction() on it.

But the application does not define an implementation itself. The idea is that other people can provide their own implementations of the interface in JARs (plugins), the main application will pull those in as dependencies, and call doAction() on the JAR's implementation.

Any idea how I can do this in practice? The ideas I had were:

  • Try to autowire the implementation through Spring Boot, but for that I would need to know its package and add it to the component scan. So it would mean putting requirements on the naming of the "plugin" jar. Something I would prefer not to do.

  • With plain Java my first idea was to keep a registry of implementations (e.g. a Set&lt;Interface A&gt;), but the plugin wouldn't be able to access the registry -- it would be a dependency cycle.

What I'm doing right now is defining a Rest API that the "plugin" needs to implement, deploy the plugin in the same environment and the main application just makes the calls through the Rest API.
But for performance reasons I'm looking for a solution with more direct calls that doesn't involve communication over the network. Any suggestions?

答案1

得分: 0

你需要的是Service Provider Interface (SPI)。

假设你有一个包含4个模块的项目,具体结构如下:

.
├── app
│   ├── build.gradle.kts
│   └── src
│       └── main
│           └── java
│               └── com/example/app/App.java
├── plugin
│   ├── build.gradle.kts
│   └── src
│       └── main
│           └── java
│               └── com/example/plugin/Animal.java
├── plugin-cat
│   ├── build.gradle.kts
│   └── src
│       └── main
│           ├── java
│           │   └── com/example/cat/Cat.java
│           └── resources
│               └── META-INF/services/com.example.plugin.Animal
├── plugin-dog
│   ├── build.gradle.kts
│   └── src
│       └── main
│           ├── java
│           │   └── com/example/dog/Dog.java
│           └── resources
│               └── META-INF/services/com.example.plugin.Animal
└── settings.gradle.kts

接口如下:

// Animal.java
public interface Animal {
    String kind();
}

以及它的两个实现:

// Cat.java
public class Cat implements Animal {
    @Override
    public String kind() {
        return "cat";
    }
}
// Dog.java
public class Dog implements Animal {
    @Override
    public String kind() {
        return "dog";
    }
}

然后在每个实现插件的resources文件夹下放置一个名为com.example.plugin.Animal的文件。文件中包含其实现类的规范名称,一行一个:

plugin-cat/src/main/resources/META-INF/services/com.example.plugin.Animal:

com.example.cat.Cat

plugin-dog/src/main/resources/META-INF/services/com.example.plugin.Animal:

com.example.dog.Dog

你可以选择在app/build.gradle.kts中包含哪些依赖:

dependencies {
    implementation(project(":plugin"))
    implementation(project(":plugin-dog"))
    // implementation(project(":plugin-cat"))
}

对于此配置,主类App.java如下:

public class App {
    public Animal getAnimal() {
        var animal = ServiceLoader.load(Animal.class).findFirst();
        assert animal.isPresent();
        return animal.get();
    }

    public static void main(String[] args) {
        System.out.println(new App().getAnimal().kind());
    }
}

将在控制台上打印出dog

这是用于此演示的项目链接:https://github.com/chehsunliu/stackoverflow/tree/main/a.2023-05-10.gradle.75359691。


参考资料

英文:

What you need is Service Provider Interface (SPI).

Assume you have a 4-module project with the following structure:

.
├── app
│&#160;&#160; ├── build.gradle.kts
│&#160;&#160; └── src
│&#160;&#160;     └── main
│&#160;&#160;         └── java
│&#160;&#160;          &#160;&#160; └── com/example/app/App.java
├── plugin
│&#160;&#160; ├── build.gradle.kts
│&#160;&#160; └── src
│&#160;&#160;     └── main
│&#160;&#160;         └── java
│&#160;&#160;          &#160;&#160; └── com/example/plugin/Animal.java
├── plugin-cat
│&#160;&#160; ├── build.gradle.kts
│&#160;&#160; └── src
│&#160;&#160;     └── main
│&#160;&#160;         ├── java
│&#160;&#160;         │&#160;&#160; └── com/example/cat/Cat.java
│&#160;&#160;         └── resources
│&#160;&#160;             └── META-INF/services/com.example.plugin.Animal
├── plugin-dog
│&#160;&#160; ├── build.gradle.kts
│&#160;&#160; └── src
│&#160;&#160;     └── main
│&#160;&#160;         ├── java
│&#160;&#160;         │&#160;&#160; └── com/example/dog/Dog.java
│&#160;&#160;         └── resources
│&#160;&#160;             └── META-INF/services/com.example.plugin.Animal
└── settings.gradle.kts

The interface looks like:

// Animal.java
public interface Animal {
    String kind();
}

and its two implementations:

// Cat.java
public class Cat implements Animal {
    @Override
    public String kind() {
        return &quot;cat&quot;;
    }
}
// Dog.java
public class Dog implements Animal {
    @Override
    public String kind() {
        return &quot;dog&quot;;
    }
}

Then put a file named com.example.plugin.Animal under each implementation plugin's resources folder. The file contains the canonical name of their implementation class in one line:

plugin-cat/src/main/resources/META-INF/services/com.example.plugin.Animal:

com.example.cat.Cat

plugin-dog/src/main/resources/META-INF/services/com.example.plugin.Animal:

com.example.dog.Dog

You can choose which dependency to include in app/build.gradle.kts:

dependencies {
    implementation(project(&quot;:plugin&quot;))
    implementation(project(&quot;:plugin-dog&quot;))
    // implementation(project(&quot;:plugin-cat&quot;))
}

For this config, the main class App.java:

public class App {
    public Animal getAnimal() {
        var animal = ServiceLoader.load(Animal.class).findFirst();
        assert animal.isPresent();
        return animal.get();
    }

    public static void main(String[] args) {
        System.out.println(new App().getAnimal().kind());
    }
}

will print dog to the console.

Here is the project for this demo: https://github.com/chehsunliu/stackoverflow/tree/main/a.2023-05-10.gradle.75359691.


References

huangapple
  • 本文由 发表于 2023年2月6日 17:49:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75359691.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定