教你搭建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的讲解。本文未经允许禁止转载。