1. 程式人生 > >用 Spring Boot 實現電商系統 Web API (二)創建多模塊項目

用 Spring Boot 實現電商系統 Web API (二)創建多模塊項目

ble jin play 正常 ota autowired ips 功能 bind

大型項目,需要將代碼按不同功能,分成不同模塊,這樣比較好管理和閱讀代碼,也有助於多人協作。

一、項目結構

1.1 模塊說明

項目分成5個模塊,分別如下:

模塊名稱 說明
webapi HTTP接口層,對外提供 restful api
service 服務層
repo 數據訪問層
common 公用層
util 工具類層

1.2 創建模塊

模塊分層在實質是以文件目錄的形式實現的,新建一個名為“commerce”的目錄,然後在裏邊創建五跟文件夾,名字為“commerce-webapi”“commerce-service”、“commerce-repo”、“commerce-common”、“commerce-util”。在每個模塊名前加“commerce-”前綴,是為了更好的和他模塊區分,還有個一個是,每個模塊都會單獨生成一個jar包,加前綴也是為了避免和外部引用的jar包重名。

創建好上述的五個文件夾後,再在“commerce”目錄新建兩個名為 “settings.gradle”和“build.gradle”的純文本文件,在每個子目錄中都建一個名為“build.gradle”的純文本文件。

在“setting.gradle”加入如下內容:

rootProject.name=commerce

include commerce-util
include commerce-common
include commerce-repo
include commerce-service
include commerce-webapi

第一行“rootProject.name=‘commerce‘”是可選,不加入也可,setting.gradle文件內容主要是為了說明項目中包含哪些模塊。所有的“build.gradle”文件都留空白,稍後我們再添加內容。最後,建立的文件和目錄如下:

技術分享

這樣,一個多模塊的 Gradle 項目就建成了。

1.3 檢查模塊

在“commerce”目錄中執行如下命令

gradle projects

如果顯示內容如下,說明我gradle已經認出我們創建的項目了。

技術分享

二、添加代碼

2.1創建代碼目錄

在每個子模塊中,添加用於存放Java代碼的目錄,目錄統一為

src
  +-main
      +-java

技術分享

2.2 包名規則

對於Java包名,我們統一規範為:頂級包名.項目名.模塊名,頂級包名可以隨意取,比如:hang.commerce.webapi。

2.3 各個模塊代碼

為了更清晰說明各個模塊是如何組織起來的,我們在每個模塊都加入代碼,然後讓他們互相引用。

我們假設要實現這樣一個API:通過GET請求接口,接口返回一個用戶的信息,用戶信息中包含用戶名和最後的登錄時間。

2.3.1 根目錄

在根目錄的 build.gradle 文件加入如下內容:

subprojects{
    apply plugin:java
    sourceCompatibility=1.8
    group=hang.commerce
    version=0.0.1

    repositories{
        jcenter();
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }

    dependencies{

    }

    sourceSets{
        main{
            resources{
                srcDirs=[src/main/resources, src/main/java]
            }
        }
    }

    tasks.withType(JavaCompile) {
        options.encoding = UTF-8
    }
}

這裏主要是對各個子項目(模塊)進行通用配置,避免在每個模塊中重復配置。

2.3.2 commerce-repo 模塊

添加 User.java作為數據層的實體類

commerce/commerce-repo/src/main/java/hang/commerce/repo/User.java

package hang.commerce.repo;

import org.springframework.stereotype.Component;

/**
 * 用戶類
 */
@Component
public class User {

    /**
     * 名稱
     */
    private String name;

    /**
     * 最後登錄時間
     */
    private String lastLoginTime;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLastLoginTime() {
        return lastLoginTime;
    }

    public void setLastLoginTime(String lastLoginTime) {
        this.lastLoginTime = lastLoginTime;
    }
}

在User類上,我使用了“@Component”註解,這樣就可以使用Spring Boot的依賴註入(IOC)功能,在使用的地方用“@Autowired”註解來註明要註入的地方。

由於要使用Component這個註解類,所以我們還需要在 commerce-repo 模塊中的 build.gradle 加入如下內容以引入JAR包。

commerce/commerce-repo/build.gradle

dependencies {
    compile "org.springframework:spring-context:5.0.0.RELEASE"
}

2.3.3 commerce-util 模塊

我們需要格式化用戶最後的登錄時間,在util模塊增加一個時間工具類。

commerce/commerce-util/src/main/java/hang/commerce/util/DateTimeTool.java

package hang.commerce.util;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 時間工具類
 */
public class DateTimeTool {

    /**
     * 日期格式化
     * @param time 日期
     * @return yyyy-MM-dd HH:mm:ss 格式的日期
     */
    public static String format(Date time){
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
    }
}

commerce-util 模塊的 build.gradle 文件不需要增加任何內容。

2.3.4 commerce-service 模塊

服務模塊從 repo模塊取數據,然後給webapi模塊提供數據。在本示例中,我們只是簡單提供一個 get() 方法,返回User對象。為了指明這個是一個服務,我們在 UserService 類上加了 “@Service” 註解,這樣Spring會自動完成依賴註入,在需要 Service 的地方,通過 “@Autowired” 註解註入 ,比如,我們需要User對象時,不是直接 new 出來,而是通過 Autowired 註入。代碼中,設置用戶的最後登錄時間時,用到了 “commerce-util”的 “DateTimeTool”工具類中的方法。

commerce/commerce-service/src/main/java/hang/commerce/service/UserService.java

package hang.commerce.service;

import hang.commerce.repo.User;
import hang.commerce.util.DateTimeTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class UserService {

    @Autowired
    private User user;

    public User get(){
        user.setName("Hello");
        user.setLastLoginTime(DateTimeTool.format(new Date()));
        return user;
    }

}

在 service 層,我們就使用到了 gradle 的模塊間引用功能。gradle 模塊間的引用只需要一行代碼就可以實現,

commpile project(:模塊名)

模塊名稱前的“:”是分隔符,相當於Linux系統中的文件路徑中 “/”,第一個“:”表示根目錄。

由於“commerce-service”模塊使用到了“commerce-repo”模塊和“commmerce-util”模塊中的代碼,所以,我們需要在 “build.gradle”加入依賴。

對外部Jar包的依賴,寫法如下

compile "org.springframework:spring-context:5.0.0.RELEASE"

規則是 compile “組織名稱:包名:版本號”,gradle 會自動根據 build.gradle 文件中的 repositories{} 的配置到網上下載Jar包,緩存到本地。

在 commerce-service 模塊中,buidl.gradle 的寫法如下:

commerce/commerce-service/build.gradle

dependencies {
    compile project(:commerce-repo)
    compile project(:commerce-util)
    compile "org.springframework:spring-context:5.0.0.RELEASE"
}

2.3.5 commerce-common 模塊

為了規範API的返回值格式,我們在 commerce-common 模塊增加了API返回值類

commerce/commerce-common/src/main/java/hang/commerce/common/ApiResult.java

package hang.commerce.common;

/**
 * API返回值對象
 * @param <T> 返回的數據類型
 */
public class ApiResult<T> {

    /**
     * 狀態碼
     */
    private ApiResultCode code;

    /**
     * 返回的消息字符串
     */
    private String message;

    /**
     * 返回的數據
     */
    private T data;

    /**
     * 異常名稱
     */
    private String exception;

    /**
     * 訪問路徑
     */
    private String path;

    public ApiResultCode getCode() {
        return code;
    }

    public void setCode(ApiResultCode code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

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

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getException() {
        return exception;
    }

    public void setException(String exception) {
        this.exception = exception;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

commerce/commerce-common/src/main/java/hang/commerce/common/ApiResultCode.java

package hang.commerce.common;

/**
 * API 返回值狀態碼
 */
public enum ApiResultCode {

    /**
     * 成功
     */
    OK,

    /**
     * 失敗
     */
    Failed,

    /**
     * 未找到資源
     */
    NotFound,

    /**
     * 未授權
     */
    NotAuth
}

由於 commerce-common 模塊沒有使用其他模塊或者外部的Jar包,所以 commerce-common模塊的 build.gradle 文件不需要增加任何內容。

2.3.6 commerce-webapi 模塊

commerce-webapi 模塊是入口模塊,需要配置 spring boot。由於我們使用了Spring Boot的依賴註入功能,所以我們需要在入口類上加上註解指明要IOC要掃描的包範圍,如下

@ComponentScan(value = "hang.commerce") // 依賴註入掃描

commerce/commerce-webapi/src/main/java/hang/commerce/webapi/WebApiApplication.java

package hang.commerce.webapi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * spring boot 應用入口
 */
@ComponentScan(value = "hang.commerce") // 依賴註入掃描
@SpringBootApplication
public class WebApiApplication {
    public static void main(String[] args){
        SpringApplication.run(WebApiApplication.class, args);
    }
}

我們將所有的API統一放到 controller 包中。

使用

@RestController

指明,這是 restfule api。

使用

@RequestMapping("api/home")

指明 API的前綴。

在 Controller中,需要使用到服務層的服務時,只需要寫如下代碼即可,Spring Boot IOC 會自動幫我們處理余下的事情。

@Autowired
private UserService userService;

controller的代碼如下:

commerce/commerce-webapi/src/main/java/hang/commerce/webapi/controller/HomeController.java

package hang.commerce.webapi.controller;

import hang.commerce.common.ApiResult;
import hang.commerce.common.ApiResultCode;
import hang.commerce.repo.User;
import hang.commerce.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("api/home")
@RestController
public class HomeController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public ApiResult<User> get(){
        User user = userService.get();
        ApiResult<User> result = new ApiResult<>();
        result.setCode(ApiResultCode.OK);
        result.setMessage("獲取成功");
        result.setData(user);

        return result;
    }
}

在commerce-webapi模塊中的 build.gradle 和正常的Spring Boot程序配置無異,只是增加了對項目中其他模塊的引用

compile project(:commerce-common)
compile project(:commerce-util)
compile project(:commerce-repo)
compile project(:commerce-service)

最後,build.gradle 的配置如下

commerce/commerce-webapi/build.gradle

buildscript {
    ext {
        springBootVersion = 2.0.0.M5
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

group = hang.commerce
version = 0.0.1
sourceCompatibility = 1.8

apply plugin: java
apply plugin: eclipse
apply plugin: org.springframework.boot
apply plugin: io.spring.dependency-management

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}

dependencies{
    compile project(:commerce-common)
    compile project(:commerce-util)
    compile project(:commerce-repo)
    compile project(:commerce-service)
    compile(org.springframework.boot:spring-boot-starter-web)
}

至此,整個項目的代碼和配置已經全部完成。

三、編譯運行

3.1 編譯

在 commerce 目錄中執行

gradle build

即可進行編譯。

3.2 運行

在 commerce 目錄中執行

gradle bootRun

即可運行整個項目。

四、gradle wrapper

如果別人的電腦上沒裝有gradle,那麽就無法編譯和運行我們的程序,為了解決這個問題,我們可以把gradle附帶到代碼中。

在 commerce 目錄中執行

gradle wrapper

之後,gradle會在目錄中加入

commerce
  +--gradlew
  +--gradlew.bat
  +--gradle
     +--wrapper
        +--gradle-wrapper.jar
        +--gradle-wrapper.properties

這樣,當別人拿到我們的代碼後,不需要安裝 gradle 程序,只需要把之前的命令“gradle”改成 “gradlew”就可以了,比如

gradlew build

當執行 “gradlew”命令時,會根據 gradle/wrapper/gradlw-wrapper.properties文件中的配置,到網上下載 gradle 程序,存放在 用戶目錄/.gradle/wrapper/dists/gradle-x.x.x-bin 目錄,比如

distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip

會到網上下載 gradle-4.2.1-bin.zip 存放到 用戶目錄/.gradle/wrapper/dists/gradle-4.2.1-bin/隨機字符串/gradle-4.2.1/

五、總結

1、在根目錄中加入 settings.gradle 的文本文件,文件內容寫明子模塊(子文件夾名),比如

include commerce-util
include commerce-common
include commerce-repo
include commerce-service
include commerce-webapi

2、根目錄加入 build.gradle 的文本文件,作為項目和各個子模塊的通用配置。

3、每個子模塊都加入 build.gradle 的文本文件,作為子模塊的配置。

4、各個模塊間的引用通過在 dependencies{} 中加入 compile project(“:子模塊名”),比如:

dependencies{
    compile project(:commerce-common)
    compile project(:commerce-util)
    compile project(:commerce-repo)
    compile project(:commerce-service)
    compile(org.springframework.boot:spring-boot-starter-web)
}

六、附錄

源碼 https://github.com/jinghang/commerce/tree/master/0x02/commerce

用 Spring Boot 實現電商系統 Web API (二)創建多模塊項目