教你搭建SpringBoot多模块框架

教你搭建springboot多模块框架

1. 写在前面

看到这里的小伙伴注意了,本文将介绍利用SpringBoot快速搭建一个拥有多模块的Web服务框架,并且附带返回值统一封装、统一异常处理、跨域配置、异步处理配置、多种工具类源
码,可以供大家学习,文中如果有不懂的地方可以留言,博主全时段在线为您解答疑难。
系统架构图

2. 系统结构

这里首先介绍一下系统结构,作为多模块的服务架构,其基本封装都应该放置在基础模块中,供其它模块应用,本博文介绍的服务框架也是如此,所有基础代码封装于base模块之中。一个服务端框架,每个模块之间的依赖关系就是一个系统结构,这里分析一个系统,假设现在要做一个学生管理系统(老掉牙的系统,但是举例子还是挺受用),具体情况列举如下:

2.1 需求分析

首先,我们要做这样一个学生管理系统,我们要明确需要实现哪些功能,这里博主为了贴近多模块应用,暂时将功能设计的更多一点,其具体需求如下:

(1)学生信息管理:对学生信息进行管理
(2)学生成绩管理:学生成绩的管理
(3)学生选课管理:学生课程选择,课程信息维护

上面三个看似有联系,但是实际上这些功能又相对独立,内部虽然有一定的联系,但是我们完全可以把三个模块独立抽取出来,设定三个独立模块(这里是为举例,逻辑上可能会出现问题):

(1)学生信息管理----模块名称 infomation
(2)学生成绩管理----模块名称 achievement
(3)学生选课管理----模块名称 curriculum

最终,设计项目架构如下图(为了举例设计的架构):
学生管理系统架构
由上图可以看出,系统分为六个基本模块,base、db、core模块是直接引用,infomation、achievement、curriculum三个模块并列,需求分析暂时搁置在这里。

2.2 代码结构

这里设计了一个简单的系统,并进行了基本的封装,其基本结构如下图所示:
代码结构图
从图中可以看出共有七个模块,引入一个 index 模块用作启动。

2.3 代码分析

这一小节对base模块中配置与相关封装的代码进行展示与解释。

2.3.1 统一返回值封装

package com.frame.yihao.base.response.util;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.frame.yihao.base.response.enumeration.HttpStatusEnum;
import com.frame.yihao.base.response.enumeration.ResponseMessageEnum;
import com.frame.yihao.base.response.enumeration.SystemStatusEnum;
import com.google.common.collect.Sets;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author: darryl
 * @Date: 2019/12/26 13:20
 * 返回值统一封装
 */
@Data
@ApiModel("系统返回值统一封装")
public class ResponseMessage<T> implements Serializable {

    /**
     * 异常消息
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty("异常消息")
    protected String message;

    /**
     * 业务状态码
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty("业务状态码")
    protected String code;

    /**
     * 响应值
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty("响应值")
    protected T result;

    /**
     * 状态码
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(value = "状态码", required = true)
    protected int status;

    /**
     * 响应内容的字段
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty("响应内容的字段")
    protected LinkedHashSet<String> fields;

    /**
     * 时间戳
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(value = "时间戳", required = true, dataType = "Long")
    protected Long timestamp;

    /**
     * 包括
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(hidden = true)
    protected transient Map<Class<?>, Set<String>> includes;

    /**
     * 排除
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(hidden = true)
    protected transient Map<Class<?>, Set<String>> excludes;

    /**
     * 业务逻辑异常
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> serviceException(String message) {
        return error(HttpStatusEnum.OK.getValue(), SystemStatusEnum.UNKNOWN.getCode(), message);
    }

    /**
     * 用户未登录
     *
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> unLogin() {
        return error(HttpStatusEnum.OK.getValue(), SystemStatusEnum.NOLOGGING.getCode(), ResponseMessageEnum.USER_UN_LOGIN.getCode());
    }

    /**
     * 无权限,需要消息提示
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> unauthorized(String message) {
        return error(HttpStatusEnum.OK.getValue(), SystemStatusEnum.UNAUTHORIZED.getCode(), message);
    }

    /**
     * 无权限
     *
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> unauthorized() {
        return error(HttpStatusEnum.OK.getValue(), SystemStatusEnum.UNAUTHORIZED.getCode(), ResponseMessageEnum.NO_ACCESS.getCode());
    }

    /**
     * 请求失败,包含失败消息
     *
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(String message) {
        return error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getValue(), SystemStatusEnum.UNKNOWN.getCode(), message);
    }

    /**
     * 请求失败。包含失败消息与失败消息args
     *
     * @param message
     * @param args
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(String message, Object... args) {
        return error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getValue(), message, args);
    }

    /**
     * 请求失败
     *
     * @param status
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(int status, String message) {
        return error(status, SystemStatusEnum.UNKNOWN.getCode(), message);
    }

    /**
     * 请求失败
     *
     * @param status
     * @param message
     * @param args
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(int status, String message, Object... args) {
        return error(status, SystemStatusEnum.UNKNOWN.getCode(), message, args);
    }

    /**
     * 请求失败
     *
     * @param status
     * @param code
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(int status, String code, String message) {
        return error(status, code, message, null);
    }

    /**
     * 请求失败
     *
     * @param status
     * @param code
     * @param message
     * @param args
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> error(int status, String code, String message, Object... args) {
        ResponseMessage<T> msg = new ResponseMessage<>();
        msg.message = message;
        msg.status(status);
        msg.code(code);
        return msg.putTimeStamp();
    }

    /**
     * 请求成功,不包含响应值
     */
    public static <T> ResponseMessage<T> ok() {
        return ok(null);
    }

    /**
     * 请求成功,包含响应值
     *
     * @param result
     * @param <T>
     * @return
     */
    public static <T> ResponseMessage<T> ok(T result) {
        return (new ResponseMessage<T>()).result(result).putTimeStamp().code(SystemStatusEnum.SUCCESS.getCode()).status(HttpStatusEnum.OK.getValue());
    }

    /**
     * 设置响应值
     *
     * @param status
     * @return
     */
    public ResponseMessage<T> status(int status) {
        this.status = status;
        return this;
    }

    /**
     * 设置业务逻辑代码
     *
     * @param code
     * @return
     */
    public ResponseMessage<T> code(String code) {
        this.code = code;
        return this;
    }

    /**
     * 设置结果值
     *
     * @param result
     * @return
     */
    public ResponseMessage<T> result(T result) {
        this.result = result;
        return this;
    }

    /**
     * 设置时间戳
     *
     * @return
     */
    private ResponseMessage<T> putTimeStamp() {
        this.timestamp = System.currentTimeMillis();
        return this;
    }

    /**
     * 响应内容字段
     *
     * @param fields
     * @return
     */
    public ResponseMessage<T> fields(LinkedHashSet<String> fields) {
        this.fields = fields;
        return this;
    }

    /**
     * 相应内容字段组
     *
     * @param field
     * @return
     */
    public ResponseMessage<T> field(String field) {
        if (this.fields == null) {
            synchronized (this) {
                if (this.fields == null) {
                    this.fields = Sets.newLinkedHashSet();
                }
            }
        }
        this.fields.add(field);
        return this;
    }

    /**
     * 重写toString
     *
     * @return
     */
    @Override
    public String toString() {
        return JSON.toJSONStringWithDateFormat(this, "yyyy-MM-dd HH:mm:ss", new com.alibaba.fastjson.serializer.SerializerFeature[0]);
    }

    /**
     * @param type
     * @param fields
     * @return
     */
    public ResponseMessage<T> include(Class<?> type, String... fields) {
        return include(type, Arrays.asList(fields));
    }

    /**
     * @param type
     * @param fields
     * @return
     */
    public ResponseMessage<T> include(Class<?> type, Collection<String> fields) {
        if (this.includes == null) {
            this.includes = new HashMap<>();
        }
        if (fields == null || fields.isEmpty()) {
            return this;
        }
        fields.forEach(field -> {
            if (field.contains(".")) {
                String[] tmp = field.split("[.]", 2);
                try {
                    Field field1 = type.getDeclaredField(tmp[0]);
                    if (field1 != null) {
                        include(field1.getType(), new String[]{tmp[1]});
                    }
                } catch (Throwable throwable) {
                }
            } else {

                getStringListFromMap(this.includes, type).add(field);
            }
        });
        return this;
    }

    /**
     * @param type
     * @param fields
     * @return
     */
    public ResponseMessage<T> exclude(Class type, Collection<String> fields) {
        if (this.excludes == null) {
            this.excludes = new HashMap<>();
        }
        if (fields == null || fields.isEmpty()) {
            return this;
        }
        fields.forEach(field -> {
            if (field.contains(".")) {
                String[] tmp = field.split("[.]", 2);
                try {
                    Field field1 = type.getDeclaredField(tmp[0]);
                    if (field1 != null) {
                        exclude(field1.getType(), new String[]{tmp[1]});
                    }
                } catch (Throwable throwable) {
                }
            } else {

                getStringListFromMap(this.excludes, type).add(field);
            }
        });
        return this;
    }

    /**
     * @param fields
     * @return
     */
    public ResponseMessage<T> exclude(Collection<String> fields) {
        Class<?> type;
        if (this.excludes == null) {
            this.excludes = new HashMap<>();
        }
        if (fields == null || fields.isEmpty()) {
            return this;
        }

        if (getResult() != null) {
            type = getResult().getClass();
        } else {
            return this;
        }
        exclude(type, fields);
        return this;
    }

    /**
     * @param fields
     * @return
     */
    public ResponseMessage<T> include(Collection<String> fields) {
        Class<?> type;
        if (this.includes == null) {
            this.includes = new HashMap<>();
        }
        if (fields == null || fields.isEmpty()) {
            return this;
        }

        if (getResult() != null) {
            type = getResult().getClass();
        } else {
            return this;
        }
        include(type, fields);
        return this;
    }

    /**
     * @param type
     * @param fields
     * @return
     */
    public ResponseMessage<T> exclude(Class type, String... fields) {
        return exclude(type, Arrays.asList(fields));
    }

    /**
     * @param fields
     * @return
     */
    public ResponseMessage<T> exclude(String... fields) {
        return exclude(Arrays.asList(fields));
    }

    /**
     * @param fields
     * @return
     */
    public ResponseMessage<T> include(String... fields) {
        return include(Arrays.asList(fields));
    }

    /**
     * @param map
     * @param type
     * @return
     */
    protected Set<String> getStringListFromMap(Map<Class<?>, Set<String>> map, Class<?> type) {
        return map.computeIfAbsent(type, k -> new HashSet());
    }
}

上文中的统一返回值可以在Web项目中起到重要作用,前后端沿用统一格式,开发更加优化。

2.3.2 统一异常处理

package com.frame.yihao.base.exception.config;

import com.frame.yihao.base.exception.custom.ServiceException;
import com.frame.yihao.base.exception.custom.UserUnLoginException;
import com.frame.yihao.base.response.enumeration.HttpStatusEnum;
import com.frame.yihao.base.response.enumeration.ResponseMessageEnum;
import com.frame.yihao.base.response.util.ResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.ValidationException;
import java.util.Objects;

/**
 * @Author: darryl
 * @Date: 2019/12/26 15:01
 * 系统异常捕获配置
 */
@ResponseBody
@ControllerAdvice
public class SystemExceptionConfig {

    private Logger log = LoggerFactory.getLogger(SystemExceptionConfig.class);

    /**
     * 系统业务逻辑异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public ResponseMessage handleServiceException(ServiceException e) {
        log.error(ResponseMessageEnum.BUSINESS_LOGIC_EXCEPTION.getCode() + e.getMessage());
        return ResponseMessage.serviceException(e.getMessage());
    }

    /**
     * 系统参数验证异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ValidationException.class)
    public ResponseMessage handleValidationException(ValidationException e) {
        log.error(ResponseMessageEnum.PARAMETER_VALIDATION_EXCEPTION.getCode() + e.getMessage());
        return ResponseMessage.serviceException(e.getMessage());
    }

    /**
     * 参数检验异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ResponseMessage handBindException(BindException e) {
        log.error(ResponseMessageEnum.PARAMETER_VALIDATION_EXCEPTION.getCode() + e.getMessage());
        return ResponseMessage.serviceException(e.getMessage());
    }

    /**
     * 用户未登录异常捕获
     *
     * @param e
     * @return
     */
    @ExceptionHandler(UserUnLoginException.class)
    public ResponseMessage handUserUnLoginException(UserUnLoginException e) {
        log.error(ResponseMessageEnum.USER_UN_LOGIN.getCode() + e.getMessage());
        return ResponseMessage.unLogin();
    }

    /**
     * 使用优雅验证时参数校验的异常信息
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error(ResponseMessageEnum.PARAMETER_VALIDATION_EXCEPTION.getCode(), e);
        return ResponseMessage.serviceException(String.format("%s:%s", Objects.requireNonNull(e.getBindingResult().getFieldError()).getField(), e.getBindingResult().getFieldError().getDefaultMessage()));
    }

    /**
     * 参数校验时,参数数量不对应的异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseMessage handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error(ResponseMessageEnum.REQUEST_PARAMETER_MISSING.getCode(), e.getMessage());
        return ResponseMessage.error(HttpStatusEnum.BAD_REQUEST.getValue(), ResponseMessageEnum.REQUEST_PARAMETER_MISSING.getCode());
    }

    /**
     * 参数解析失败异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseMessage handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error(ResponseMessageEnum.PARAMETER_PARSING_FAILED.getCode(), e.getMessage());
        return ResponseMessage.error(HttpStatusEnum.BAD_REQUEST.getValue(), ResponseMessageEnum.PARAMETER_PARSING_FAILED.getCode());
    }

    /**
     * 不支持当前请求方法异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseMessage handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.error(ResponseMessageEnum.METHOD_NOT_SUPPORTED.getCode(), e.getMessage());
        return ResponseMessage.error(HttpStatusEnum.METHOD_NOT_ALLOWED.getValue(), ResponseMessageEnum.METHOD_NOT_SUPPORTED.getCode());
    }

    /**
     * 空指针异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseMessage handNullPointerException(NullPointerException e) {
        log.error(ResponseMessageEnum.SYSTEM_NULL_POINTER.getCode() + e);
        e.printStackTrace();
        return ResponseMessage.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getValue(), ResponseMessageEnum.SYSTEM_NULL_POINTER.getCode());
    }

    /**
     * 捕获未捕获到的其他异常,定义为服务器内部错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseMessage handException(Exception e) {
        log.error(ResponseMessageEnum.SERVER_INTERNAL_EXCEPTION.getCode() + e);
        e.printStackTrace();
        return ResponseMessage.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getValue(), ResponseMessageEnum.SERVER_INTERNAL_EXCEPTION.getCode());
    }
}

这里的统一异常处理过程仅供参考,可以给初学者一点灵感。代码展示暂时就是这样,有需要完整代码的可以留言您的邮箱,我可以打包发到您的邮箱。

3. 多模块系统

3.1 什么是多模块

笔者认为多模块项目首先可以优化需求,将需求模块化、细节化,这样讲大需求划分为多个小需求,在团队合作开发的过程中,每个人负责一个小型的模块,将系统公共的功能抽取出来;其次多模块系统可以更有效的维护,且维护简单;多模块系统看起来结构明了,上手简单。所谓多模块,就是Maven项目里父子模块的相互引用,每个模块之间有一定的相关性。

3.2 如何创建多模块项目

笔者在创建项目时使用的 IDEA 社区版(开源免费),所以创建的流程介绍也将按照该IDE进行。

首先创建一个Maven项目,设定好groupId与artifactId,创建完成之后,删除src下所有目录,只保留pom.xml与IDEA项目下的.idea文件夹,这样第一步就完成了。
创建主项目

接着在已经创建好的项目上右键,创建一个Module,依旧选择Maven,设定好groupId与artifactId。
创建新的模块
创建完成后可以看到在主项目下的pom.xml已经自动引入了刚创建的子项目依赖。
子项目依赖图片
这样多模块项目就算创建完成了,其过程和步骤是相当简单的,如果有读者尝试创建过程中出现问题可以留言,笔者会及时解答。

3.3 多模块间的引用

程序入口,入口模块需要引入所有包含Controller的模块,项目启动时会扫描这些模块,然后依次读取依赖。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>student-management-system</artifactId>
        <groupId>yihao</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>student-managment-index</artifactId>

    <dependencies>
        <dependency>
            <groupId>yihao</groupId>
            <artifactId>student-managment-achievement</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>yihao</groupId>
            <artifactId>student-managment-curriculum</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>yihao</groupId>
            <artifactId>student-managment-infomation</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</project>

依赖引入时按照引入Maven依赖的方法进行引入,引入格式如下:

<dependency>
    <groupId>yihao</groupId>	
    <artifactId>student-managment-curriculum</artifactId>
    <version>${project.version}</version>
</dependency>

在模块引入时,切记依赖环路引入,如下图所示:
依赖引入出现环路
当出现环路引入依赖时,就会报异常信息:

Error:java: Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [student-managment-base,student-managment-db] are excluded from annotation processing

当出现这种错误信息就可以排查依赖引用是否出现问题。

4. 写在后面

本文讲解了多模块项目的创建,以及一些代码的分享,有需要的读者可以留言留下邮箱,博主会将完整代码发送到你们留言的邮箱里。文中如有不当之处希望读者指出。觉得博主分享确实有用的伙伴可以点个赞关注下博主,后期博主还会更新Vue、java等相关的更多知识点,以及HTTP协议的讲解、网络基础的讲解、Spring5的讲解。本文未经允许禁止转载。

发布了8 篇原创文章 · 获赞 20 · 访问量 6103

猜你喜欢

转载自blog.csdn.net/weixin_43071717/article/details/104282553