基于SpringBoot搭建应用开发框架1

448 阅读9分钟

Spring的简史

第一阶段:XML配置,在Spring1.x时代,使用Spring开发满眼都是xml配置的Bean,随着项目的扩大,我们需要把xml配置文件分放到不同的配置文件里,那时候需要频繁的在开发的类和配置文件之间切换。

第二阶段:注解配置,在Spring2.x时代,Spring提供声明Bean的注解,大大减少了配置量。应用的基本配置用xml,业务配置用注解。

第三阶段:Java配置,从Spring3.x到现在,Spring提供了Java配置,使用Java配置可以让你更理解你所配置的Bean。

Spring Boot:使用“习惯优于配置”的理念让你的项目快速运行起来。使用Spring Boot很容易创建一个独立运行、准生产级别的基于Spring框架的项目,使用Spring Boot你可以不用或者只需要很少的Spring配置。

下面就来使用Spring Boot一步步搭建一个前后端分离的应用开发框架,并且以后不断的去完善这个框架,往里面添加功能。后面以实战为主,不会介绍太多概念,取而代之的是详细的操作。

零、开发技术简介

开发平台:windows

开发工具:Intellij IDEA 2017.1

JDK:Java 8

Maven:maven-3.3.9

服务器:tomcat 8.0

数据库:MySQL 5.7

数据源:Druid1.1.6

缓存:Redis 3.2

日志框架:SLF4J+Logback

Spring Boot:1.5.9.RELEASE

ORM框架:MyBatis+通用Mapper

Spring Boot官方文档:Spring Boot Reference Guide

一、创建项目

这一节创建项目的基础结构,按照spring boot的思想,将各个不同的功能按照starter的形式拆分开来,做到灵活组合,并简单介绍下Spring Boot相关的东西。

1、创建工程

① 通过File > New > Project,新建工程,选择Spring Initializr,然后Next。

② 尽量为自己的框架想个好点的名字,可以去申请个自己的域名。我这里项目名称为Sunny,项目路径为com.lyyzoo.sunny。

③ 这里先什么都不选,后面再去集成。注意我的Spring Boot版本为1.5.9。Next

④ 定义好工程的目录,用一个专用目录吧,不要在一个目录下和其它东西杂在一起。之后点击Finish。

上面说的这么详细,只有一个目的,从一个开始就做好规范。

⑤ 生成的项目结构如下,可以自己去看下pom.xml里的内容。

2、创建Starter

先创建一个core核心、cache缓存、security授权认证,其它的后面再集成进去。

跟上面一样的方式,在Sunny下创建sunny-starter-core、sunny-starter-cache、sunny-starter-security子模块。

这样分模块后,我们以后需要哪个模块就引入哪个模块即可,如果哪个模块不满足需求,还可以重写该模块。

最终的项目结构如下:

3、启动项目

首先在core模块下来启动并了解SpringBoot项目。

① 在com.lyyzoo.core根目录下,有一个SunnyStarterCoreApplication,这是SpringBoot的入口类,通常是*Application的命名。

入口类里有一个main方法,其实就是一个标准的Java应用的入口方法。在main方法中使用SpringApplication.run启动Spring Boot项目。

然后看看@SpringBootApplication注解,@SpringBootApplication是Spring Boot的核心注解,是一个组合注解。

@EnableAutoConfiguration让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置。

Spring Boot会自动扫描@SpringBootApplication所在类的同级包以及下级包里的Bean。

② 先启动项目,这里可以看到有一个Spring Boot的启动程序,点击右边的按钮启动项目。看到控制台Spring的标志,就算是启动成功了。

③ 替换默认的banner

可以到patorjk.com/software/ta…

4、Spring Boot 配置

① 配置文件

Spring Boot使用一个全局的配置文件application.properties或application.yaml,放置在src/main/resources目录下。我们可以在这个全局配置文件中对一些默认的配置值进行修改。

具体有哪些配置可到官网查找,有非常多的配置,不过大部分使用默认即可。Common application properties

然后,需要为不同的环境配置不同的配置文件,全局使用application-{profile}.properties指定不同环境配置文件。

我这里增加了开发环境(dev)和生产环境(prod)的配置文件,并通过在application.properties中设置spring.profiles.active=dev来指定当前环境。

② starter pom

Spring Boot为我们提供了简化开发绝大多数场景的starter pom,只要使用了应用场景所需的starter pom,无需繁杂的配置,就可以得到Spring Boot为我们提供的自动配置的Bean。

后面我们将会通过加入这些starter来一步步集成我们想要的功能。具体有哪些starter,可以到官网查看:Starters

③ 自动配置

Spring Boot关于自动配置的源码在spring-boot-autoconfigure中如下:

我们可以在application.properties中加入debug=true,查看当前项目中已启用和未启用的自动配置。

我们在application.properties中的配置其实就是覆盖spring-boot-autoconfigure里的默认配置,比如web相关配置在web包下。

常见的如HttpEncodingProperties配置http编码,里面自动配置的编码为UTF-8。

MultipartProperties,上传文件的属性,设置了上传最大文件1M。

ServerProperties,配置内嵌Servlet容器,配置端口、contextPath等等。

之前说@SpringBootApplication是Spring Boot的核心注解,但他的核心功能是由@EnableAutoConfiguration注解提供的。

@EnableAutoConfiguration注解通过@Import导入配置功能,在AutoConfigurationImportSelector中,通过SpringFactoriesLoader.loadFactoryNames扫描META-INF/spring.factories文件。

在spring.factories中,配置了需要自动配置的类,我们也可以通过这种方式添加自己的自动配置。

在spring-boot-autoconfigure下就有一个spring.factories,如下:

说了这么多,只为说明一点,Spring Boot为我们做了很多自动化的配置,搭建快速方便。

但是,正因为它为我们做了很多事情,就有很多坑,有时候,出了问题,我们可能很难找出问题所在,这时候,我们可能就要考虑下是否是自动配置导致的,有可能配置冲突了,或者没有使用上自定义的配置等等。

5、项目结构划分

core是项目的核心模块,结构初步规划如下:

 base是项目的基础核心,定义一些基础类,如BaseController、BaseService等;

    cache是缓存相关;\

    config是配置中心,模块所有的配置放到config里统一管理;

    constants里定义系统的常量。\

    exception里封装一些基础的异常类;

 system是系统模块;

    util里则是一些通用工具类;

二、基础结构功能

1、web支持

只需在pom.xml中加入spring-boot-starter-web的依赖即可。

之后,查看POM的依赖树(插件:Maven Helper),可以看到引入了starter、tomcat、web支持等。可以看出,Sping Boot内嵌了servlet容器,默认tomcat。

自动配置在WebMvcAutoConfiguration和WebMvcProperties里,可自行查看源码,一般我们不需添加其他配置就可以启动这个web项目了。

2、基础功能

在core中添加一些基础的功能支持。

① 首先引入一些常用的依赖库,主要是一些常用工具类,方便以后的开发。

<!-- ******************************* 常用依赖库 ********************************** -->
<!-- 针对开发IO流功能的工具类库 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>${commons.io.version}</version>
</dependency>
<!-- 文件上传 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>${commons.fileupload.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 常用的集合操作,丰富的工具类 -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>${commons.collections.version}</version>
</dependency>
<!-- 操作javabean的工具包 -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>${commons.beanutils.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 包含一些通用的编码解码算法. 如:MD5、SHA1、Base64等 -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>${commons.codec.version}</version>
</dependency>
<!-- 包含丰富的工具类如 StringUtils -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${commons.lang3.version}</version>
</dependency>
<!--
    Guava工程包含了若干被Google的Java项目广泛依赖的核心库. 集合[collections] 、缓存[caching] 、原生类型支持[primitives support] 、
    并发库[concurrency libraries] 、通用注解[common annotations] 、字符串处理[string processing] 、I/O 等等。
-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>${guava.version}</version>
</dependency>

版本号如下:

② 在base添加一个Result类,作为前端的返回对象,Controller的直接返回对象都是Result。

package com.lyyzoo.core.base;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.io.Serializable;

/**
 * 前端返回对象
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-28
 */
public class Result implements Serializable {
    private static final long serialVersionUID = 1430633339880116031L;

    /**
     * 成功与否标志
     */
    private boolean success = true;
    /**
     * 返回状态码,为空则默认200.前端需要拦截一些常见的状态码如403、404、500等
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Integer status;
    /**
     * 编码,可用于前端处理多语言,不需要则不用返回编码
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String code;
    /**
     * 相关消息
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String msg;
    /**
     * 相关数据
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Object data;


    public Result() {}

    public Result(boolean success) {
        this.success = success;
    }

    public Result(boolean success, Integer status) {
        this.success = success;
        this.status = status;
    }

    public Result(boolean success, String code, String msg){
        this(success);
        this.code = code;
        this.msg = msg;
    }

    public Result(boolean success, Integer status, String code, String msg) {
        this.success = success;
        this.status = status;
        this.code = code;
        this.msg = msg;
    }

    public Result(boolean success, String code, String msg, Object data){
        this(success);
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

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

之后在util添加生成Result的工具类Results,用于快速方便的创建Result对象。

package com.lyyzoo.core.util;

import com.lyyzoo.core.base.Result;

/**
 * Result生成工具类
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-28
 */
public class Results {

    protected Results() {}

    public static Result newResult() {
        return new Result();

    }

    public static Result newResult(boolean success) {
        return new Result(success);
    }

    //
    // 业务调用成功
    // ----------------------------------------------------------------------------------------------------
    public static Result success() {
        return new Result();
    }

    public static Result success(String msg) {
        return new Result(true, null, msg);
    }

    public static Result success(String code, String msg) {
        return new Result(true, code, msg);
    }

    public static Result successWithStatus(Integer status) {
        return new Result(true, status);
    }

    public static Result successWithStatus(Integer status, String msg) {
        return new Result(true, status, null, msg);
    }

    public static Result successWithData(Object data) {
        return new Result(true, null, null, data);
    }

    public static Result successWithData(Object data, String msg) {
        return new Result(true, null, msg, data);
    }

    public static Result successWithData(Object data, String code, String msg) {
        return new Result(true, code, msg, data);
    }

    //
    // 业务调用失败
    // ----------------------------------------------------------------------------------------------------
    public static Result failure() {
        return new Result(false);
    }

    public static Result failure(String msg) {
        return new Result(false, null, msg);
    }

    public static Result failure(String code, String msg) {
        return new Result(false, code, msg);
    }

    public static Result failureWithStatus(Integer status) {
        return new Result(false, status);
    }

    public static Result failureWithStatus(Integer status, String msg) {
        return new Result(false, status, null, msg);
    }

    public static Result failureWithData(Object data) {
        return new Result(false, null, null, data);
    }

    public static Result failureWithData(Object data, String msg) {
        return new Result(false, null, msg, data);
    }

    public static Result failureWithData(Object data, String code, String msg) {
        return new Result(false, code, msg, data);
    }

}

③ 在base添加BaseEnum<K, V>枚举接口,定义了获取值和描述的接口。

package com.lyyzoo.core.base;

/**
 * 基础枚举接口
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-31
 */
public interface BaseEnum<K, V> {

    /**
     * 获取编码
     *
     * @return 编码
     */
    K code();

    /**
     * 获取描述
     *
     * @return 描述
     */
    V desc();

}

然后在constants下定义一个基础枚举常量类,我们把一些描述信息维护到枚举里面,尽量不要在代码中直接出现魔法值(如一些编码、中文等),以后的枚举常量类也可以按照这种模式来写。

package com.lyyzoo.core.constants;

import com.lyyzoo.core.base.BaseEnum;

import java.util.HashMap;
import java.util.Map;

/**
 * 基础枚举值
 *
 * @version 1.0
 * @author bojiangzhou 2018-01-01
 */
public enum BaseEnums implements BaseEnum<String, String> {

    SUCCESS("request.success", "请求成功"),

    FAILURE("request.failure", "请求失败"),

    OPERATION_SUCCESS("operation.success", "操作成功"),

    OPERATION_FAILURE("operation.failure", "操作失败"),

    ERROR("system.error", "系统异常"),

    NOT_FOUND("not_found", "请求资源不存在"),

    FORBIDDEN("forbidden", "无权限访问"),

    VERSION_NOT_MATCH("record_not_exists_or_version_not_match", "记录版本不存在或不匹配"),

    PARAMETER_NOT_NULL("parameter_not_be_null", "参数不能为空");

    private String code;

    private String desc;

    private static Map<String, String> allMap = new HashMap<>();

    BaseEnums(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    static {
        for(BaseEnums enums : BaseEnums.values()){
            allMap.put(enums.code, enums.desc);
        }
    }

    @Override
    public String code() {
        return code;
    }

    @Override
    public String desc() {
        return desc;
    }

    public String desc(String code) {
        return allMap.get(code);
    }

}

④ 再添加一个常用的日期工具类对象,主要包含一些常用的日期时间格式化,后续可再继续往里面添加一些公共方法。

package com.lyyzoo.core.util;


import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

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

/**
 * 日期时间工具类
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-28
 */
public class Dates {

    /**
     * 日期时间匹配格式
     */
    public interface Pattern {
        //
        // 常规模式
        // ----------------------------------------------------------------------------------------------------
        /**
         * yyyy-MM-dd
         */
        String DATE = "yyyy-MM-dd";
        /**
         * yyyy-MM-dd HH:mm:ss
         */
        String DATETIME = "yyyy-MM-dd HH:mm:ss";
        /**
         * yyyy-MM-dd HH:mm
         */
        String DATETIME_MM = "yyyy-MM-dd HH:mm";
        /**
         * yyyy-MM-dd HH:mm:ss.SSS
         */
        String DATETIME_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
        /**
         * HH:mm
         */
        String TIME = "HH:mm";
        /**
         * HH:mm:ss
         */
        String TIME_SS = "HH:mm:ss";

        //
        // 系统时间格式
        // ----------------------------------------------------------------------------------------------------
        /**
         * yyyy/MM/dd
         */
        String SYS_DATE = "yyyy/MM/dd";
        /**
         * yyyy/MM/dd HH:mm:ss
         */
        String SYS_DATETIME = "yyyy/MM/dd HH:mm:ss";
        /**
         * yyyy/MM/dd HH:mm
         */
        String SYS_DATETIME_MM = "yyyy/MM/dd HH:mm";
        /**
         * yyyy/MM/dd HH:mm:ss.SSS
         */
        String SYS_DATETIME_SSS = "yyyy/MM/dd HH:mm:ss.SSS";

        //
        // 无连接符模式
        // ----------------------------------------------------------------------------------------------------
        /**
         * yyyyMMdd
         */
        String NONE_DATE = "yyyyMMdd";
        /**
         * yyyyMMddHHmmss
         */
        String NONE_DATETIME = "yyyyMMddHHmmss";
        /**
         * yyyyMMddHHmm
         */
        String NONE_DATETIME_MM = "yyyyMMddHHmm";
        /**
         * yyyyMMddHHmmssSSS
         */
        String NONE_DATETIME_SSS = "yyyyMMddHHmmssSSS";
    }

    public static final String DEFAULT_PATTERN = Pattern.DATETIME;

    public static final String[] PARSE_PATTERNS = new String[]{
            Pattern.DATE,
            Pattern.DATETIME,
            Pattern.DATETIME_MM,
            Pattern.DATETIME_SSS,
            Pattern.SYS_DATE,
            Pattern.SYS_DATETIME,
            Pattern.SYS_DATETIME_MM,
            Pattern.SYS_DATETIME_SSS
    };

    /**
     * 格式化日期时间
     *
     * @param date 日期时间
     *
     * @return yyyy-MM-dd HH:mm:ss
     */
    public static String format(Date date) {
        return format(date, DEFAULT_PATTERN);
    }

    /**
     * 格式化日期
     *
     * @param date 日期(时间)
     *
     * @param pattern 匹配模式 参考:{@link Dates.Pattern}
     *
     * @return 格式化后的字符串
     */
    public static String format(Date date, String pattern) {
        if (date == null) {
            return null;
        }
        pattern = StringUtils.isNotBlank(pattern) ? pattern : DEFAULT_PATTERN;
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        return sdf.format(date);
    }

    /**
     * 解析日期
     *
     * @param date 日期字符串
     *
     * @return 解析后的日期 默认格式:yyyy-MM-dd HH:mm:ss
     */
    public static Date parseDate(String date) {
        if (StringUtils.isBlank(date)) {
            return null;
        }
        try {
            return DateUtils.parseDate(date, PARSE_PATTERNS);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析日期
     *
     * @param date 日期
     *
     * @param pattern 格式 参考:{@link Dates.Pattern}
     *
     * @return 解析后的日期,默认格式:yyyy-MM-dd HH:mm:ss
     */
    public static Date parseDate(String date, String pattern) {
        if (StringUtils.isBlank(date)) {
            return null;
        }
        String[] parsePatterns;
        parsePatterns = StringUtils.isNotBlank(pattern) ? new String[]{pattern} : PARSE_PATTERNS;
        try {
            return DateUtils.parseDate(date, parsePatterns);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }



}

⑤ Constants定义系统级的通用常量。

package com.lyyzoo.core.constants;

import com.google.common.base.Charsets;

import java.nio.charset.Charset;

/**
 * 系统级常量类
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-28
 */
public class Constants {

    public static final String APP_NAME = "sunny";

    /**
     * 系统编码
     */
    public static final Charset CHARSET = Charsets.UTF_8;

    /**
     * 标识:是/否、启用/禁用等
     */
    public interface Flag {

        Integer YES = 1;

        Integer NO = 0;
    }

    /**
     * 操作类型
     */
    public interface Operation {
        /**
         * 添加
         */
        String ADD = "add";
        /**
         * 更新
         */
        String UPDATE = "update";
        /**
         * 删除
         */
        String DELETE = "delete";
    }

    /**
     * 性别
     */
    public interface Sex {
        /**
         * 男
         */
        Integer MALE = 1;
        /**
         * 女
         */
        Integer FEMALE = 0;
    }

}

⑥ 在base添加空的BaseController、BaseDTO、Service、Mapper,先定义好基础结构,后面再添加功能。

BaseDTO:标准的who字段、版本号、及10个扩展字段。

因为这里用到了@Transient注解,先引入java持久化包:

package com.lyyzoo.core.base;

import com.fasterxml.jackson.annotation.*;
import com.lyyzoo.core.Constants;
import com.lyyzoo.core.util.Dates;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 基础实体类
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-29
 */
public class BaseDTO implements Serializable {
    private static final long serialVersionUID = -4287607489867805101L;

    public static final String FIELD_OPERATE = "operate";
    public static final String FIELD_OBJECT_VERSION_NUMBER = "versionNumber";
    public static final String FIELD_CREATE_BY = "createBy";
    public static final String FIELD_CREATOR = "creator";
    public static final String FIELD_CREATE_DATE = "createDate";
    public static final String FIELD_UPDATE_BY = "updateBy";
    public static final String FIELD_UPDATER = "updater";
    public static final String FIELD_UPDATE_DATE = "updateDate";


    /**
     * 操作类型,add/update/delete 参考:{@link Constants.Operation}
     */
    @Transient
    private String _operate;

    /**
     * 数据版本号,每发生update则自增,用于实现乐观锁.
     */
    private Long versionNumber;

    //
    // 下面是标准 WHO 字段
    // ----------------------------------------------------------------------------------------------------
    /**
     * 创建人用户名
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Long createBy;
    /**
     * 创建人名称
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Transient
    private String creator;
    /**
     * 创建时间
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonFormat(pattern = Dates.DEFAULT_PATTERN)
    private Date createDate;

    /**
     * 更新人用户名
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Long updateBy;
    /**
     * 更新人名称
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Transient
    private String updater;
    /**
     * 更新时间
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonFormat(pattern = Dates.DEFAULT_PATTERN)
    private Date updateDate;

    /**
     * 其它属性
     */
    @JsonIgnore
    @Transient
    protected Map<String, Object> innerMap = new HashMap<>();

    //
    // 下面是扩展属性字段
    // ----------------------------------------------------------------------------------------------------

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute1;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute2;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute3;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute4;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute5;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute6;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute7;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute8;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute9;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String attribute10;

    public String get_operate() {
        return _operate;
    }

    public void set_operate(String _operate) {
        this._operate = _operate;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    }

    public String toJSONString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
    }

    public Long getVersionNumber() {
        return versionNumber;
    }

    public void setVersionNumber(Long versionNumber) {
        this.versionNumber = versionNumber;
    }

    public Long getCreateBy() {
        return createBy;
    }

    public void setCreateBy(Long createBy) {
        this.createBy = createBy;
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        this.creator = creator;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Long getUpdateBy() {
        return updateBy;
    }

    public void setUpdateBy(Long updateBy) {
        this.updateBy = updateBy;
    }

    public String getUpdater() {
        return updater;
    }

    public void setUpdater(String updater) {
        this.updater = updater;
    }

    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }

    @JsonAnyGetter
    public Object getAttribute(String key) {
        return innerMap.get(key);
    }

    @JsonAnySetter
    public void setAttribute(String key, Object obj) {
        innerMap.put(key, obj);
    }

    public String getAttribute1() {
        return attribute1;
    }

    public void setAttribute1(String attribute1) {
        this.attribute1 = attribute1;
    }

    public String getAttribute2() {
        return attribute2;
    }

    public void setAttribute2(String attribute2) {
        this.attribute2 = attribute2;
    }

    public String getAttribute3() {
        return attribute3;
    }

    public void setAttribute3(String attribute3) {
        this.attribute3 = attribute3;
    }

    public String getAttribute4() {
        return attribute4;
    }

    public void setAttribute4(String attribute4) {
        this.attribute4 = attribute4;
    }

    public String getAttribute5() {
        return attribute5;
    }

    public void setAttribute5(String attribute5) {
        this.attribute5 = attribute5;
    }

    public String getAttribute6() {
        return attribute6;
    }

    public void setAttribute6(String attribute6) {
        this.attribute6 = attribute6;
    }

    public String getAttribute7() {
        return attribute7;
    }

    public void setAttribute7(String attribute7) {
        this.attribute7 = attribute7;
    }

    public String getAttribute8() {
        return attribute8;
    }

    public void setAttribute8(String attribute8) {
        this.attribute8 = attribute8;
    }

    public String getAttribute9() {
        return attribute9;
    }

    public void setAttribute9(String attribute9) {
        this.attribute9 = attribute9;
    }

    public String getAttribute10() {
        return attribute10;
    }

    public void setAttribute10(String attribute10) {
        this.attribute10 = attribute10;
    }

}

同时,重写了toString方法,增加了toJsonString方法,使得可以格式化输出DTO的数据:

直接打印DTO,输出的格式大概就是这个样子:

⑦ 在exception添加BaseException,定义一些基础异常类

基础异常类都继承自运行时异常类(RunntimeException),尽可能把受检异常转化为非受检异常,更好的面向接口编程,提高代码的扩展性、稳定性。

BaseException:添加了一个错误编码,其它自定义的异常应当继承该类。

package com.lyyzoo.core.exception;

/**
 * 基础异常类
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-31
 */
public class BaseException extends RuntimeException {
    private static final long serialVersionUID = -997101946070796354L;

    /**
     * 错误编码
     */
    protected String code;

    public BaseException() {}

    public BaseException(String message) {
        super(message);
    }

    public BaseException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() {
        return code;
    }

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

ServiceException:继承BaseException,Service层往Controller抛出的异常。

package com.lyyzoo.core.exception;

/**
 * Service层异常
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-31
 */
public class ServiceException extends BaseException {
    private static final long serialVersionUID = 6058294324031642376L;

    public ServiceException() {}

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String code, String message) {
        super(code, message);
    }

}

3、添加系统用户功能,使用Postman测试接口

① 在system模块下,再分成dto、controller、service、mapper、constants子包,以后一个模块功能开发就是这样一个基础结构。

User:系统用户

package com.lyyzoo.core.system.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.lyyzoo.core.base.BaseDTO;
import com.lyyzoo.core.util.Dates;

import java.util.Date;

/**
 * 系统用户
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-31
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends BaseDTO {
    private static final long serialVersionUID = -7395431342743009038L;

    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 生日
     */
    @JsonFormat(pattern = Dates.Pattern.DATE)
    private Date birthday;
    /**
     * 性别:1-男/0-女
     */
    private Integer sex;
    /**
     * 是否启用:1/0
     */
    private Integer enabled;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Integer getEnabled() {
        return enabled;
    }

    public void setEnabled(Integer enabled) {
        this.enabled = enabled;
    }

}

UserController:用户控制层;用@RestController注解,前后端分离,因为无需返回视图,采用Restful风格,直接返回数据。

package com.lyyzoo.core.system.controller;

import com.lyyzoo.core.Constants;
import com.lyyzoo.core.base.BaseController;
import com.lyyzoo.core.base.BaseEnums;
import com.lyyzoo.core.base.Result;
import com.lyyzoo.core.system.dto.User;
import com.lyyzoo.core.util.Dates;
import com.lyyzoo.core.util.Results;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * 用户Controller
 *
 * @version 1.0
 * @author bojiangzhou 2017-12-31
 */
@RequestMapping("/sys/user")
@RestController
public class UserController extends BaseController {

    private static List<User> userList = new ArrayList<>();

    // 先静态模拟数据
    static {
        User user1 = new User();
        user1.setUserId(1L);
        user1.setUsername("lufei");
        user1.setNickname("蒙奇D路飞");
        user1.setBirthday(Dates.parseDate("2000-05-05"));
        user1.setSex(Constants.Sex.MALE);
        user1.setEnabled(Constants.Flag.YES);
        userList.add(user1);

        User user2 = new User();
        user2.setUserId(2L);
        user2.setUsername("nami");
        user2.setNickname("娜美");
        user2.setBirthday(Dates.parseDate("2000/7/3"));
        user2.setSex(Constants.Sex.FEMALE);
        user2.setEnabled(Constants.Flag.YES);
        userList.add(user2);
    }

    @RequestMapping("/queryAll")
    public Result queryAll(){
        return Results.successWithData(userList, BaseEnums.SUCCESS.code(), BaseEnums.SUCCESS.description());
    }

    @RequestMapping("/queryOne/{userId}")
    public Result queryOne(@PathVariable Long userId){
        User user = null;
        for(User u : userList){
            if(u.getUserId().longValue() == userId){
                user = u;
            }
        }
        return Results.successWithData(user);
    }
}

② Postman请求:请求成功,基础的HTTP服务已经实现了。