创建和导入自定义 Spring 库,同时分离共享依赖项

Creating and importing a custom Spring library whilst separating shared dependencies

我希望创建一个 Spring 库项目以在内部团队中共享。

在非常基本的概念层面上,库会将消息事件发送到队列,我的计划是在一个团队中跨多个 Spring 将此标准化,引导微服务以相同的方式发送消息。

我在库项目中的 pom 看起来像这样

<artifactId>my-library</artifactId>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

etc...

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.1.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.16.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.2.Final</version>
    </dependency>
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>3.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>2.2.6</version>
    </dependency>

我在图书馆项目中有一个看起来像这样的服务

public class EventService {

  Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

  public void sendAuditEvent(AuditMessage auditMessage){

    Set<ConstraintViolation<AuditMessage>> violations = validator.validate(auditMessage);

    if(!isEmpty(violations)){
      log.error("Unable to send audit message");
      violations.stream().forEach( v-> log.error(v.getMessage()));
    }
    log.info("Found {} violations", violations.size());
    // etc blah blah
    return;
  }
}

当我将库导入另一个项目时,我的想法是我可以自动装配 EventService。通过将它添加到 pom 然后 @ComponentScan({"my.library.package.eventlibrary.service"})

如何防止 spring 版本锁定?如果库今天使用 spring 2.1.5.RELEASE 而导入库的项目使用不同的版本,我不会以潜在的 maven 冲突结束吗?

也可以说导入库的项目使用较低版本的休眠 api 并且库有 6.0.16.Final。我如何防止项目使用在库类路径中找到的较新的项目?

为了进一步澄清我的问题,有没有一种方法可以将库中的依赖项与使用它的项目分开。

Pre Java 9. 您可以在声明对模块的依赖项时使用 maven 排除 spring 依赖项,Hibernate 也是如此。但是你不能告诉你的模块在 WAR 中使用不同的休眠版本。

如果你想解决这个问题,你可以将你的库开发为独立的微服务,以 REST 或 Websocket 的形式公开接口,如果你想要全双工通信或其他 JMS 等等......

Post Java 9 您可以使用 java 模块化来为您的 jar 模块定义确切的依赖关系。检查拼图项目 https://www.baeldung.com/project-jigsaw-java-modularity

在您的情况下,为了拥有同一库的不同版本(休眠)。您将需要两个单独的 class 装载机。为此,您需要使用此处阅读的分层 http://openjdk.java.net/projects/jigsaw/spec/sotms/#layers

这里是许多示例的源代码,包括使用图层的示例。关注他们:https://github.com/accso/java9-jigsaw-examples/tree/master/jigsaw-examples

您可以尝试排除您的库可以带给将使用它的项目的所有传递依赖项。

为此,您应该将 dependencyManagement 部分中的 spring-boot-starter-parent 替换为 spring-boot-dependencies,并对库需要的所有依赖项使用 provided 范围与项目一起工作并且将被项目完全使用的,将与库一起工作。

例如,您图书馆的 pom.xml 可以是这样的:

<!-- ... -->
    <groupId>com.example</groupId>
    <artifactId>library</artifactId>
    <version>0.1.0</version>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <spring-boot.version>2.1.5.RELEASE</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
<!-- ... -->

然后您将能够在不同的项目中使用您的库,例如使用旧的 Spring Boot:

<!-- ... -->
    <groupId>com.example</groupId>
    <artifactId>old-project</artifactId>
    <version>0.13.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.19.RELEASE</version>
        <relativePath/>
    </parent>
<!-- ... -->
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>library</artifactId>
            <version>0.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
<!-- ... -->    

因此该项目将使用其 spring-boot-starter-web 中的 hibernate-validator:5.3.6.Final

重要说明 - 您的库代码应该 'compatible' 与此版本的 Spring Boot.换句话说,您应该使用您感兴趣的 Spring Boot 的不同版本来测试您的库。

以我的project为例。

可能不是您想要的,但您可以将您的库作为 spring-boot-starter auto configuration 模块分发(当然,如果客户端是 spring 启动应用程序)。

通过这种方式,您可以灵活地控制依赖项,并让您的客户更自由地使用该库。

在您的特定情况下,如果您需要向队列发送消息,您肯定需要在类路径中有相应的 类。使用自动配置,您可以根据 Class Conditions or Been Conditions 跟踪您的客户端在运行时是否具有正确的配置。如果出现问题(提供有意义的错误消息),您也可以使上下文加载失败。

Spring 还提供了跟踪机制,用于跟踪如果缺少特定 class/library 可能发生的情况。