用 Spring Boot 实现电商系统 Web API (二)创建多模块项目

大型项目,需要将代码按不同功能,分成不同模块,这样比较好管理和阅读代码,也有助于多人协作。

一、项目结构

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”文件都留空白,稍后我们再添加内容。最后,建立的文件和目录如下:

image

这样,一个多模块的 Gradle 项目就建成了。

 

1.3 检查模块

在“commerce”目录中执行如下命令

gradle projects

如果显示内容如下,说明我gradle已经认出我们创建的项目了。

image

 

二、添加代码

2.1创建代码目录

在每个子模块中,添加用于存放Java代码的目录,目录统一为

src
  +-main
      +-java

image

 

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

posted @ 2017-10-27 18:31  teafree  阅读(9326)  评论(0编辑  收藏  举报