英文:
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<Interface A>
), 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。
参考资料
- https://stackoverflow.com/questions/13254620/meta-inf-services-in-jar-with-gradle
- https://www.baeldung.com/java-spi
英文:
What you need is Service Provider Interface (SPI).
Assume you have a 4-module project with the following structure:
.
├── 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
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 "cat";
}
}
// Dog.java
public class Dog implements Animal {
@Override
public String kind() {
return "dog";
}
}
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(":plugin"))
implementation(project(":plugin-dog"))
// implementation(project(":plugin-cat"))
}
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论