本指南向您展示了如何使用Spring引导创建多模块项目,包括一个库JAR和一个使用这个库的主应用程序。您可以了解如何自己构建一个库(即,一个JAR文件)。

您将设置一个简单的库jar,它为的hello world消息提供一个公开服务,然后将该服务包含在一个Web应用程序中,涉及到Maven和Gradle。

创建一个根项目
创建如下的目录结构

在您选择的项目目录中,创建以下子目录结构;例如,在*nix系统上使用mkdir library应用程序:

└── library
└── application

在项目的根目录中,您将需要建立一个构建系统,本指南将向您展示如何使用Maven或Gradle构建。
Gradle配置
我们建议使用Gradle包装。因此,可以通过从现有项目复制包装器来安装包装器,或者通过使用包装器任务执行Gradle来安装包装器(按照Gradle文档中的说明操作)。这将为您提供一个顶级目录,如下所示:

└── library
└── application
└── gradlew
└── gradlew.bat
└── gradle
    └── wrapper
        └── gradle-wrapper.properties
        └── gradle-wrapper.jar

Windows(非Cygwin)用户执行gradlew.bat脚本来构建项目;其他环境都使用gradlew脚本。然后将settings.gradle添加到根目录以在顶层驱动生成:
settings.gradle

rootProject.name = 'gs-multi-module'

include 'library'
include 'application'

Maven配置
我们建议使用Maven包装。因此,通过从现有项目中复制包装器,或者使用io.takari:maven:wrapper目标执行maven来安装包装器(按照包装器文档中的说明操作)。这将为您提供一个顶级目录,如下所示:

└── library
└── application
└── mvnw
└── mvnw.cmd
└── .mvn
    └── wrapper
        └── maven-wrapper.properties
        └── maven-wrapper.jar

Windows(非cygwin)用户执行mvnw.cmd脚本来构建项目;其他环境都使用mvnw脚本。然后将pom.xml添加到根目录以在顶层驱动构建:
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>org.springframework</groupId>
    <artifactId>gs-multi-module</artifactId>
    <version>0.1.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>library</module>
        <module>application</module>
    </modules>

</project>

创建一个库项目
目录结构

└── src
    └── main
        └── java
            └── hello
                └── service

现在我们需要配置一个构建工具(Maven或Gradle)。在这两种情况下,请注意Springboot插件根本没有在库项目中使用。插件的主要功能是创建一个可执行的jar,这个库中不需要。为了快速启动,以下是完整的配置:

Gradles配置
library/build.gradle

buildscript {
    repositories { mavenCentral() }
}

plugins { id "io.spring.dependency-management" version "1.0.5.RELEASE" }

ext { springBootVersion = '2.1.6.RELEASE' }

apply plugin: 'java'
apply plugin: 'eclipse'

jar {
    baseName = 'gs-multi-module-library'
    version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8

repositories { mavenCentral() }

dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
    imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") }
}

虽然没有使用SpringBoot Gradle插件,但您确实希望利用SpringBoot依赖性管理,以便使用dependency-management插件和SpringBoot中的mavenBom进行配置。
Maven配置
library/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>gs-multi-module-library</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

虽然没有使用Spring Boot Maven插件,但您确实希望利用Spring Boot依赖性管理功能。另一种选择是在POM的<dependencyManagement/>部分将依赖关系管理作为BOM导入。

创建服务组件
这库将提供一个myservice类,该类可供其它应用程序使用:
library/src/main/java/hello/service/MyService.java

package hello.service;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

@Service
@EnableConfigurationProperties(ServiceProperties.class)
public class MyService {

    private final ServiceProperties serviceProperties;

    public MyService(ServiceProperties serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    public String message() {
        return this.serviceProperties.getMessage();
    }
}

要使其在标准的Springboot中可配置,还可以添加@ConfigurationProperties类:
library/src/main/java/hello/service/ServiceProperties.java

package hello.service;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("service")
public class ServiceProperties {

    /**
     * A message for the service.
     */
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

这个不是必须的,一个库类可能提供纯的JAVA Api,没有Spring boot相关的,在这种情况下,使用库的应用程序需要提供配置本身。
测试这个服务组件
您将要为这个库组件编写单元测试。如果您将可重用的Spring配置作为库的一部分提供,那么您可能还需要编写一个集成测试,以确保配置正常工作。为此,可以使用JUnit 和@SpringBootTest 注释。例如:
library/src/test/java/hello/service/MyServiceTest.java

package hello.service;

import static org.assertj.core.api.Assertions.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest("service.message=Hello")
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    public void contextLoads() {
        assertThat(myService.message()).isNotNull();
    }

    @SpringBootApplication
    static class TestConfiguration {
    }

}

在上面的示例中,我们使用@SpringBootTest注释的默认属性为测试配置了service.message。不建议将application.properties放入库中,因为使用它的应用程序在运行时可能会发生冲突(从类路径只加载一个application.properties)。您可以将application.properties放在测试类路径中,但不能将其包含在jar中,例如将其放在src/test/resources中。

创建Application类
目录结构如下

└── src
    └── main
        └── java
            └── hello
                └── app

不要使用与库中的包相同的包,除非您希望通过@ComponentScan在应用程序中默认包含库中的所有Spring组件。现在生成配置出现了:
Application的Maven配置
application/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>gs-multi-module-application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>gs-multi-module-library</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot将会你做如下的事:

  • 将 classpath 里面所有用到的jar包构建成一个可执行的 JAR 文件,方便执行你的程序
  • 搜索public static void main()方法并且将它当作可执行类
  • 根据springboot版本,去查找相应的依赖类版本,当然你可以定义其它版本。

Application代码
application/src/main/java/hello/app/DemoApplication.java

package hello.app;

import hello.service.MyService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication(scanBasePackages = "hello")
@RestController
public class DemoApplication {

    private final MyService myService;

    public DemoApplication(MyService myService) {
        this.myService = myService;
    }

    @GetMapping("/")
    public String home() {
        return myService.message();
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

因为DemoApplication与MyService(hello.service)位于不同的包(hello.app)中,@SpringBootApplication最初不会检测到它。有不同的方法可以选择MyService:

  • 通过@Import(MyService.class)直接引入
  • 使用@SpringBootApplication(scanBasePackageClasses={…​})扫描

本例按名称指定父包hello,来扫描

如果您的应用程序也使用JPA或Spring Data,请注意,在未明确指定时,@EntityScan和@EnableJpaRepositories注释仅从SpringBootApplication 继承其基包。也就是说,一旦指定了scanBasePackageClasses 或 scanBasePackages,可能还需要显式地提供@EntityScan和@EnableJpaRepositories ,并对它们的包扫描进行显式配置。

配置application.properties

service.message=Hello World

运行和测试程序
通过启动应用程序测试端到端结果。您可以很容易地在您的IDE中启动应用程序,或者按照下面的说明使用命令行。在浏览器中访问,http://localhost:8080/。在这里,您应该看到返回`Hello World`。

Gradle下:

$ ./gradlew build && ./gradlew :application:bootRun

Maven下:

$ ./mvnw install && ./mvnw spring-boot:run -pl application

祝贺你!您刚刚使用了Springboot来创建一个可重用的库,然后使用它来构建一个应用程序。