使用Gradle javafxplugin时,ServiceLoader无法定位实现

cvxl0en2  于 2022-12-13  发布在  Java
关注(0)|答案(1)|浏览(139)

我正在实现一个核心部分与GUI分离并在运行时作为服务加载的程序。为了在运行时发现实现,我花了几个星期的时间。为了尝试隔离问题,我从这里选择了一个最小的ServiceLoader示例https://reflectoring.io/service-provider-interface,并将其扩展到我的项目结构中。我的项目的GUI需要这个插件,但运行ServiceLoader的代码不需要。我使用的是org.openjfx.javafxplugin的0.0.10版本,最后一个版本是0.0.13,但这会导致另一个问题,即主类再也找不到了。如果build.gradle中没有请求该插件,ServiceLoader代码将工作,加载实现,程序将给出预期的输出。当build.gradle中请求javaxplugin时,程序不再工作。
有人有什么建议吗?我真的被卡住了,因为这是一个JavaFX应用程序,我需要那个插件。
该项目是一个Gradle项目,包含3个子项目(模块):API、实现(核心)和应用程序(GUI)。相关文件如下。
美国石油module-info.java:

module tlapi {
  exports com.chesolver.spi;
  exports com.chesolver;
}

奇怪的是,如果我启用javafx插件,编译器会引发错误com.chesolver.spi.Library: module tlapi does not declare 'uses',这对我来说很奇怪,因为模块tlapi是api,而com.chesolver.spi.Library是此模块中包含的接口的一部分。
API版本.gradle:

plugins {
    id 'tubesheetlayout.java-library-conventions'
}

核心模块信息:

module tlcore {
    requires tlapi;
}

核心建筑。Gradle:

plugins {
    id 'tubesheetlayout.java-library-conventions'
    id 'org.javamodularity.moduleplugin' version '1.8.9'
}

dependencies {
    implementation project(':api')
}

应用模块信息:

module tlclient {
    requires tlapi;
}

应用程序构建器。gradle:

plugins {
    id 'tubesheetlayout.java-application-conventions'

    // *NOTICE* if uncommented the ServiceLoader code does not work
    //id 'org.openjfx.javafxplugin' version '0.0.13'
    
    //id 'org.javamodularity.moduleplugin' version '1.8.9'
}

dependencies {
    implementation project(':api')
    implementation project(':core')
}

application {
    mainClass = "com.chesolver.library.LibraryClient"
}
2ul0zpep

2ul0zpep1#

您的主要问题(错误地指定服务模块)以及如何解决该问题的资源

对于Java平台模块化应用程序,需要在模块中使用Java平台模块化服务定义。这意味着在module-info.java中使用providesuses语句。
您的代码和service tutorial you linked并没有将服务集成到Java平台模块系统中。教程中讨论的Maven模块系统(可能还有Gradle模块)是完全不同的。
要了解有关模块化服务的信息,请阅读以下网址中的“服务”部分:

  • 模块系统的状态。
  • Jenkov模块教程,其中解释了module-info.javaprovidesuses语句。
  • ServiceLoader javadoc,应彻底审查和理解。

Baeldung为模块化服务教程提供了example code,这比我在这个答案中提供的要简单。但是Baeldung教程没有演示绑定多个服务实现、jlink或使用从JavaFX应用程序加载的服务模块,这就是为什么我在这里添加了这些内容的示例。

重要建议

关于这一点,我的建议是:* 不要使用服务机制,除非你知道你需要它 *。

示例解决方案

我知道这个问题是专门针对Gradle的,但我实际上并不使用该工具。我将使用Maven提供一个替代解决方案。它的某些方面将直接移植到Gradle,而其他方面则需要您进行调整。
解决方案由一个多模块得Maven项目组成,每个子模块对应一个Java平台模块,有一个父pom.xml指定所有得子模块,所有得子模块继承自父pom.xml。
所涉及的子模块如下:

    • 形状服务 *:ShapeFactory接口,可以创建形状。
    • 圈提供者 *:提供圆的ShapeFactory实现。
    • 平方提供者 *:一个提供正方形的ShapeFactory实现。
    • 形状应用程序 *:这是一个JavaFX应用程序,它加载可用的ShapeFactory服务提供程序并使用它们生成形状。

从组合框中选择形状工厂服务提供程序,然后单击“Create Shape”,所选提供程序将用于生成形状,然后显示该形状。
我会张贴在这里的代码,不幸的是,有很多它:-(

  • 在Idea中构建和运行 *

您可以将maven项目从根目录导入到Idea IDE中。该项目将作为单个Idea项目加载,其中包含多个Idea项目模块。当您从IDE运行主ShapeApplication类时,IDE将自动生成所有模块并为应用程序提供服务。

  • 从命令行生成和运行 *

要构建所有内容,请在项目的根目录下运行mvn clean install。要创建jlinked应用,请转到shape-app目录并运行mvn javafx:jlink

$ tree
.
├── circle-provider
│   ├── circle-provider.iml
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               ├── com
│               │   └── example
│               │       └── shapeservice
│               │           └── circleprovider
│               │               └── CircleProvider.java
│               └── module-info.java
├── pom.xml
├── shape-app
│   ├── pom.xml
│   ├── shape-app.iml
│   └── src
│       └── main
│           └── java
│               ├── com
│               │   └── example
│               │       └── shapeapp
│               │           └── ShapeApplication.java
│               └── module-info.java
├── shape-service
│   ├── pom.xml
│   ├── shape-service.iml
│   └── src
│       └── main
│           └── java
│               ├── com
│               │   └── example
│               │       └── shapeservice
│               │           └── ShapeFactory.java
│               └── module-info.java
├── shapes.iml
└── square-provider
    ├── pom.xml
    ├── square-provider.iml
    └── src
        └── main
            └── java
                ├── com
                │   └── example
                │       └── shapeservice
                │           └── squareprovider
                │               └── SquareProvider.java
                └── module-info.java

.iml只是idea模块项目文件,您可以忽略它们。
父项 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>shapes</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>shapes</name>

    <modules>
        <module>shape-service</module>
        <module>circle-provider</module>
        <module>square-provider</module>
        <module>shape-app</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <javafx.version>19</javafx.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>19</source>
                    <target>19</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • 形状服务 *

第一个

  • 圈子提供者 *

第一个

  • 平方提供者 *

第一个

  • 形状应用程序 *

第一个

注意事项

  • 像这样把东西分成模块和服务会使事情变得更加复杂。

  • 很多小事情都可能出错,尤其是如果忘记更改名称,复制和粘贴时出现的错别字。

  • 由于服务模块是动态发现的,因此编译器不会发现一些拼写错误。

  • 您不能在模块间混用套件名称。

  • 使用jlink进行链接比较麻烦,因为您需要确保服务位于jlinked模块路径上,并且您需要指示jlink绑定它们。

  • 这些工具在错误消息中有点迟钝。

  • 如果配置不正确,将找不到模块,系统将认为没有可用的匹配服务。

  • 实际上,在每次链接之前,将 * 所有 * 模块安装到Maven库中,否则它可能会拾取旧版本或找不到您的软件(这可能实际上没有必要,但在我的经验中似乎是这样)。

  • 如果你只使用默认的bind-services选项,绑定服务会使你的jlink图像变得很大。显然你可以只绑定列出的服务,而不是所有的服务,但是我找不到如何使用javafx-maven-plugin来实现这一点。你可以使用jlink命令行来获得比maven插件更细粒度的控制,尽管这会更痛苦。

  • 如果您没有在javafx-maven-plugin中显式地放置MODULEPATH设置,它将不会找到您的服务模块。

  • 请始终检查jlink详细输出,以确保绑定了所有预期得服务.

  • 当处理像这样的多模块项目时,我强烈建议将所有模块保持在同一版本号(在父pom.xml中集中配置),否则很容易链接到过时的版本。不过,这不是这个示例项目的设置方式。要固定版本,请在父pom.xml中定义一个shape.version属性,只要存在1.0-SNAPSHOT,将其替换为${shape-version},则所有项目将始终使用相同的版本。

  • 代码假设所有的东西都在模块化环境中运行,没有任何东西从类路径运行。如果你想让服务在类路径上运行,那么你需要做更多的工作(例如在MANIFEST.MF文件中定义服务元数据)。我建议只支持100%模块化环境,除非你绝对也必须支持类路径的执行。

  • 我相信还有更微妙和潜在的问题,我没有在这里提到。

相关问题