前言
🍊缘由
经常看到网上很多优秀的开源项目中,代码简洁,模块分层完美。反观自己代码层级混乱,,却不知如何整理分层。此文手把手教你一步一步创建模块,左手右手一个慢动作。结合本人实际开发及个人项目中分层思路的分享,大神请勿喷。
⏲️本文阅读时长
约25分钟
🎯主要目标
- 熟练掌握SpringBoot项目分层思路,丝滑拆分模块
- 熟悉代码层级依赖,规范化管理模块分布
- 手把手实战学习,理论实践相结合
👨🎓试用人群
- 对于Springboot熟悉但是不知道合理分层小白
- 有自己分层思路可以互相分享学习
🎁快速链接
公众号:JavaDog程序狗
在公众号,发送【分层】 ,无任何套路即可获得
或访问https://blog.javadog.net/archives/boot-module
🍩水图
下图反面教材,传统单体应用,结构臃肿
下图分层截选自本人的一个小项目,模块清晰,分工明确
我们要实现的小栗子的分层
正文
🥫1.IDEA新建项目
起名第一步,一个好名字,说不定是个好的开始
假如我们的项目是个聊天相关的项目,英文对应chat,所以定义项目名为chat-boot,其他的以此效仿
点击New->project
选择Maven项目,并选择合适JDK版本,点击Next
录入项目名称,并填写GAV坐标,点击Finish
删除无用文件及目录,如src目录和*.iml文件
删除后项目目录
修改pom.xml中依赖,增加spring-boot-starter-parent
org.springframework.boot
spring-boot-starter-parent
2.3.1.RELEASE
🌭2.创建子模块-dependencies(依赖层)
右击项目chat-boot,new ->Moudle新建模块chat-boot-dependencies
选择对应Module SDK版本,本人选择jdk1.8
填写子模块名 chat-boot-dependencies,然后检查对应GAV,点击Finish
生成子模块chat-boot-dependencies如下图
删除chat-boot-dependencies下无用文件及目录,如src目录,删除无用目录如下
完善chat-boot-dependencies下pom.xml依赖, 常用依赖放入,作为依赖主体,以下是本狗常用依赖,可酌情选择;记得把packaging改为pom
chat-boot
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-dependencies
pom
8
8
UTF-8
UTF-8
8.0.17
1.1.21
3.4.1
1.2.75
5.5.8
1.18.12
4.2.0
2.9.2
2.9.2
2.0.4
4.4.5.B
1.3.2
3.2.0
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
mysql
mysql-connector-java
${mysql-connector-java.version}
com.alibaba
druid
${druid.version}
com.alibaba
fastjson
${fastjson.version}
cn.hutool
hutool-all
${hutool.version}
org.projectlombok
lombok
${lombok.versin}
io.springfox
springfox-swagger2
${springfox-swagger2.version}
io.springfox
springfox-swagger-ui
${springfox-swagger-ui.version}
com.github.xiaoymin
knife4j-spring-boot-starter
${knife4j.version}
cn.afterturn
easypoi-base
${easypoi.version}
com.github.binarywang
weixin-java-miniapp
${weixin.version}
org.apache.shiro
shiro-spring
${shiro.version}
com.auth0
java-jwt
${jwt.version}
🎯重点
此处用的标签是 dependencyManagement,
dependencyManagement只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
🍪3.创建子模块-main(主启动层)
右击项目chat-boot,new ->Moudle新建模块chat-boot-main
选择对应Module SDK版本,本狗选择jdk1.8,点击Next
填写子模块名 chat-boot-main,然后检查对应GAV,点击Finish
生成子模块chat-boot-main如下图
完善chat-boot-main模块下pom.xml中依赖
- 引入必要依赖
- 完善profiles标签中环境相关
- 配置build标签中插件
chat-boot
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-boot-main
8
8
net.javadog.chat
chat-boot-dependencies
1.0-SNAPSHOT
pom
import
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
io.springfox
springfox-swagger2
io.springfox
springfox-swagger-ui
com.github.xiaoymin
knife4j-spring-boot-starter
chat
org.springframework.boot
spring-boot-maven-plugin
repackage
true
src/main/webapp
false
src/main/resources
true
src/main/java
**/*.xml
local
local
true
dev
dev
prod
prod
🎯重点
- 配置引入依赖chat-boot-dependencies,用作此模块依赖引入
net.javadog.chat
chat-boot-dependencies
1.0-SNAPSHOT
pom
import
- 配置build标签用于完善插件plugins,其中包含maven-compiler-plugin和maven-resources-plugin
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}
org.apache.maven.plugins
maven-resources-plugin
2.6
@
false
src/main/webapp
false
src/main/resources
true
src/main/java
**/*.xml
- 配置profiles环境变量标签,用于方便打包切换,本狗设置了 local、dev、prod三种环境
local
local
true
dev
dev
prod
prod
操作可在IDEA右上角方便切换环境
💥切记一定主动Reload一下Maven依赖
💥切记一定主动Reload一下Maven依赖
💥切记一定主动Reload一下Maven依赖
在chat-boot-main模块中加入启动类,在src/main/java下右键New=>Java Class
录入启动类名ChatApplication
完善ChatApplication启动类代码
package net.javadog.chat;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author: hdx
* @Date: 2023-01-28 11:24
* @version: 1.0
**/
@SpringBootApplication
@ServletComponentScan
@Slf4j
@EnableSwagger2
@EnableKnife4j
public class ChatApplication {
public static void main(String[] args) throws UnknownHostException {
// 启动类
ConfigurableApplicationContext application = SpringApplication.run(ChatApplication.class, args);
// 打印基础信息
info(application);
}
static void info(ConfigurableApplicationContext application) throws UnknownHostException {
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
String active = env.getProperty("spring.profiles.active");
String contextPath = env.getProperty("server.servlet.context-path");
if (contextPath == null) {
contextPath = "";
}
log.info("n----------------------------------------------------------nt" +
"欢迎访问 thttps://blog.javadog.netnt" +
"示例程序【" + active + "】环境已启动! 地址如下:nt" +
"Local: tthttp://localhost:" + port + contextPath + "nt" +
"External: thttp://" + ip + ':' + port + contextPath + 'n' +
"Swagger文档: thttp://" + ip + ":" + port + contextPath + "/doc.htmln" +
"----------------------------------------------------------");
}
}
配置application.yml文件
application.yml
#============================#
# server 配置
#============================#
server:
port: 82
max-http-header-size: 10240
servlet:
context-path: /chat/v1
#============================#
# spring 配置
#============================#
spring:
application:
# 应用名
name: chat
profiles:
active: @spring.active@
servlet:
multipart:
max-request-size: 100MB
max-file-size: 100MB
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss.SSS
locale: zh_CN
serialization:
# 格式化输出
indent_output: false
main:
allow-circular-references: true
#解决swagger版本路径不兼容问题
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#============================#
# mybatisplus 配置
#============================#
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazy-loading-enabled: true
multiple-result-sets-enabled: true
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
banner: false
db-config:
id-type: assign_id
table-underline: true
enable-sql-runner: true
#数据库类型
db-type: MYSQL
configuration-properties:
prefix:
#如果数据库为postgresql,则需要配置为blobType: BINARY
blobType: BLOB
#如果数据库为oracle或mssql,则需要配置为boolValue: 1
boolValue: true
#============================#
# logging 配置
#============================#
logging:
level:
root: info
file:
path: /root/javadog/chat/logs/${spring.application.name}/
name: ${spring.application.name}
logback:
rollingpolicy:
max-history: 7
max-file-size: 10MB
#============================#
# file 配置
#============================#
file:
# 静态附件前缀
static-prefix: attach
# 上传的文件对外暴露的访问路径
access-path-pattern: /${file.static-prefix}/**
# 文件上传目录
upload-folder: /root/javadog/chat/
# 文件上传最大
max-post-size: 10
application-local.yml
#服务配置
server:
port: 8001
max-http-header-size: 10240
# Mysql数据库
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/chat-boot?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true
username: root
password: root
redis:
host: localhost
port: 6379
password:
application-dev.yml
#服务配置
server:
port: 7001
max-http-header-size: 10240
# mybatisplus 配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
application-prod.yml
#服务配置
server:
port: 8005
🌊顺便加一下logback-spring.xml 日志文件
${application.name}
${CONSOLE_LOG_PATTERN}
${logging.path}/info/${application.name}-info.log
${logging.path}/info/${application.name}-info.%d{yyyy-MM-dd}.%i.log
${logging.file.max-size}
${logging.file.max-history}
true
${FILE_LOG_PATTERN}
warn
ACCEPT
DENY
${logging.path}/warn/${application.name}-warn.log
${logging.path}$/warn/${application.name}-warn.%d{yyyy-MM-dd}.%i.log
${logging.file.max-size}
${logging.file.max-history}
${FILE_LOG_PATTERN}
error
ACCEPT
DENY
${logging.path}/error/${application.name}-error.log
${logging.path}/error/${application.name}-error.%d{yyyy-MM-dd}.%i.log
${logging.file.max-size}
${logging.file.max-history}
${FILE_LOG_PATTERN}
启动一下项目试试,启动如下证明成功。如果报错少依赖请再拉一下Maven依赖!!!
🍇4.创建子模块-module(模块层)
右击项目chat-boot,new ->Moudle新建模块chat-boot-module
填写子模块名 chat-boot-module,然后检查对应GAV,点击Finish
生成子模块chat-boot-module如下图
删除chat-boot-module下无用文件及目录,如src目录,删除无用目录如下
自此外部大框架初步搭建成功
完善chat-boot-module下pom.xml依赖,如lombok,web等必要依赖
chat-boot
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-boot-module
pom
chat-boot-common
chat-boot-controller
chat-boot-dao
chat-boot-dto
chat-boot-entity
chat-boot-service
8
8
net.javadog.chat
chat-boot-dependencies
${project.parent.version}
pom
import
org.springframework.boot
spring-boot-starter-web
com.github.binarywang
weixin-java-miniapp
org.projectlombok
lombok
cn.hutool
hutool-all
io.springfox
springfox-swagger2
io.springfox
springfox-swagger-ui
com.github.xiaoymin
knife4j-spring-boot-starter
org.apache.shiro
shiro-spring
com.auth0
java-jwt
com.baomidou
mybatis-plus-boot-starter
mysql
mysql-connector-java
一定要重新拉取依赖!
一定要重新拉取依赖!
一定要重新拉取依赖!
🍉5.创建chat-boot-module模块下对应功能分层
目前本狗分为如下6层
- common-共通层
- controller-控制器层
- dao-数据持久层
- dto-数据传输对象层
- entity-实体层
- service-业务逻辑层
依次按照上述添加模块方式进行新增子模块,本狗如下示例一个,其余都如法炮制
右击项目chat-boot-module,new ->Moudle新建模块chat-boot-common
一定看清楚父模块是否正确
一定看清楚父模块是否正确
一定看清楚父模块是否正确
确认父级模块后,点击Finish,生成chat-boot-common模块
依次按照上述方法,新建其他模块
chat-boot-controller模块
chat-boot-dao模块
chat-boot-dto模块
chat-boot-entity模块
chat-boot-service模块
总体模块雏形基本完成
🍧6.实际流程填充
- 模拟正常前端请求到后台服务调用过程,进行实际代码补充
在chat-boot-entity下新建实体类User,在src/main/java下右键New=>Java Class,录入包名及类名
package net.javadog.chat.entity;
import lombok.Data;
/**
* @author: hdx
* @Date: 2023-01-28 14:26
* @version: 1.0
**/
@Data
public class User {
private Long id;
private String username;
private String idCard;
}
在chat-boot-dto下新建目request和response,分别代表请求传输对象和返回传输对象,并分别在目录下创建UserRequest.java和UserResponse.java
package net.javadog.chat.request;
import lombok.Data;
/**
* @author: hdx
* @Date: 2023-01-28 14:59
* @version: 1.0
**/
@Data
public class UserRequest {
private Long id;
private String username;
}
package net.javadog.chat.response;
import lombok.Data;
/**
* @author: hdx
* @Date: 2023-01-28 14:59
* @version: 1.0
**/
@Data
public class UserResponse {
private Long id;
private String username;
}
在chat-boot-dao下修改chat-boot-dao模块下修改pom.xml文件依赖,引入chat-boot-entity;并增对应UserMapper.java
package net.javadog.chat.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.javadog.chat.entity.User;
/**
* 用户mapper
*
* @author: hdx
* @Date: 2023-01-10 11:43
* @version: 1.0
**/
public interface UserMapper extends BaseMapper {
}
chat-boot-module
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-boot-dao
8
8
net.javadog.chat
chat-boot-entity
${project.parent.version}
在chat-boot-service下新建目录service和impl,并在对应目录下新建UserService.java和UserServiceImpl.java,并修改chat-boot-service模块下修改pom.xml文件依赖,引入chat-boot-dto,chat-boot-dao
package net.javadog.chat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import net.javadog.chat.entity.User;
/**
* 用户接口
*
* @author: hdx
* @Date: 2023-01-10 11:53
* @version: 1.0
**/
public interface UserService extends IService {
}
package net.javadog.chat.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.javadog.chat.entity.User;
import net.javadog.chat.mapper.UserMapper;
import net.javadog.chat.service.UserService;
import org.springframework.stereotype.Service;
/**
* 用户接口实现类
*
* @author: hdx
* @Date: 2023-01-10 11:55
* @version: 1.0
**/
@Service
public class UserServiceImpl extends ServiceImpl implements UserService {
}
chat-boot-module
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-boot-service
8
8
net.javadog.chat
chat-boot-dao
${project.parent.version}
net.javadog.chat
chat-boot-dto
${project.parent.version}
在chat-boot-controller创建UserController.java,并修改chat-boot-controller模块下修改pom.xml文件依赖,引入chat-boot-dto,chat-boot-service
package net.javadog.chat.controller;
import cn.hutool.core.bean.BeanUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import net.javadog.chat.entity.User;
import net.javadog.chat.request.UserRequest;
import net.javadog.chat.response.UserResponse;
import net.javadog.chat.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 用户控制器
*
* @author: hdx
* @Date: 2022-12-07 15:48
* @version: 1.0
**/
@Api(tags = "用户控制器")
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@ApiOperation(value = "用户详情", notes = "用户详情")
@GetMapping
public UserResponse detail(UserRequest userRequest){
UserResponse userResponse = new UserResponse();
User user = userService.getById(userRequest.getId());
BeanUtil.copyProperties(user, userResponse);
return userResponse;
}
}
🎯重要补充
1.切记修改chat-boot-main下的pom.xml依赖,将chat-boot-controller模块加入
2.切记修改chat-boot-main下的pom.xml依赖,将chat-boot-dao模块加入
3.切记修改启动类ChatApplication中加入@MapperScan注解
chat-boot
net.javadog.chat
1.0-SNAPSHOT
4.0.0
chat-boot-main
8
8
net.javadog.chat
chat-boot-dependencies
1.0-SNAPSHOT
pom
import
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
io.springfox
springfox-swagger2
io.springfox
springfox-swagger-ui
com.github.xiaoymin
knife4j-spring-boot-starter
net.javadog.chat
chat-boot-controller
${project.parent.version}
net.javadog.chat
chat-boot-dao
${project.parent.version}
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}
org.apache.maven.plugins
maven-resources-plugin
2.6
@
false
src/main/webapp
false
src/main/resources
true
src/main/java
**/*.xml
local
local
true
dev
dev
prod
prod
package net.javadog.chat;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author: hdx
* @Date: 2023-01-28 11:24
* @version: 1.0
**/
@SpringBootApplication
@ServletComponentScan
@Slf4j
@EnableSwagger2
@EnableKnife4j
@MapperScan(basePackages = {"net.javadog.chat.mapper"})
public class ChatApplication {
public static void main(String[] args) throws UnknownHostException {
// 启动类
ConfigurableApplicationContext application = SpringApplication.run(ChatApplication.class, args);
// 打印基础信息
info(application);
}
static void info(ConfigurableApplicationContext application) throws UnknownHostException {
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
String active = env.getProperty("spring.profiles.active");
String contextPath = env.getProperty("server.servlet.context-path");
if (contextPath == null) {
contextPath = "";
}
log.info("n----------------------------------------------------------nt" +
"欢迎访问 thttps://blog.javadog.netnt" +
"示例程序【" + active + "】环境已启动! 地址如下:nt" +
"Local: tthttp://localhost:" + port + contextPath + "nt" +
"External: thttp://" + ip + ':' + port + contextPath + 'n' +
"Swagger文档: thttp://" + ip + ":" + port + contextPath + "/doc.htmln" +
"----------------------------------------------------------");
}
}
🦔7.示例DB更新
- 模拟正常前端请求到后台服务调用过程,进行实际代码补充
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT 'id',
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`id_card` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'JavaDog', '123123');
🧩8.访问测试
浏览器访问http://localhost:8001/chat/v1/user?id=1
测试成功如下图所示
总结
以上示例只是简单示范分层思路,其中代码逻辑实现方式有很多种,大家选取适用自己就好,希望自己的思路能对大家有帮助
如遇缺少依赖情况,一定要重新拉取依赖!
写在最后
此生两悔,悔遇见你,更悔未早遇见你,珍惜当下拥有,勿念昔日美好。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net