前言
最近一直在思考一个问题,springboot的多模块项目到底是怎么运行和运作的?
一般我们大部分的springboot项目都是单模块的项目,但是如果后续有要求开发多模块的项目应该怎么处理?于是基于这点进行了研究。
本次文章将会带大家从头到尾搭建多模块项目,讲解怎么去串接以及如何去运行
优缺点
🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️🖊️
多模块的项目,一般也叫
微服务
项目,微服务是一种架构模式或者说是一种
架构风格
,它提倡
单一应用程序
划分为一组小的服务,每个服务在其独立的自己的进程中,服务之间相互协调,互相配合,为用户提供最终价值
而我们的多模块的恰恰是这种思想
🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️🖌️
💖💖优点💖💖
❤️ 每个服务足够内聚,足够小,代码容易理解这样能聚焦一个指定的业务功能或业务需求
❤️开发简单、开发效率提高,一个服务可能就是专一的只干一件事
❤️微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的
以及其他…
🖤🖤缺点🖤🖤
🖤 开发人员要处理分布式系统的复杂性
🖤 多服务运维难度,随着服务的增加,运维的压力也在增大
🖤维护,分工合作困难
模块划分及依赖引用
模块划分
我的风格可能和别人不一样,别人一上来就会教你步骤,但是针对于这点,我们首先得明确一点,我们要搭建一个什么样的项目,针对你要搭建的项目,需要建立的模块也不一样
🖍️🖍️🖍️🖍️🖍️
以业务层划分为例,也就是我们熟知的三层架构
也就是可以划分为数据库层,web层和实现层,对应可划分的框架明细如下
模块名称模块功能范围data实体类模块,负责实体对象的构建config配置类模块,负责springboot相关的基础配置dao数据库类模块,负责数据库增删改查相关逻辑service业务实现类模块,负责具体业务实现相关逻辑web项目启用类模块,负责项目启用,接口调用相关逻辑
🖊️🖊️🖊️🖊️🖊️
除了按照我们的三层架构的进行划分模块外,我们也可以用我们具体的实际业务进行划分
- 举一个例子,按照学校为例,我们学校如果要记录数据,可以按照这些进行划分
模块名称模块功能范围student学生信息类模块,负责记录学生基本数据teacher老师信息类模块,负责记录教师相关基本数据grade学生成绩类模块,负责记录学生成绩数据那么这里我只是简单的举一个例子,那么为了演示方便,
本文章会按照三层架构的进行讲解
依赖引用
✏️✏️✏️✏️✏️
在正式搭建项目之前,我们有一个比较重要的事情需要确认,那就是我们的模块之间应该要怎么进行引用?
因为这会涉及到一个问题,那就是
循环依赖
什么是循环依赖?
循环依赖即你A模块引入了B模块,但是B模块也引入了A模块,导致报错
循环依赖有什么影响?
✨第一点,最重要,会导致项目无法启动
✨第二点,如果前期不规划好怎么引入,会导致出现一个问题,假如有A,B,C三个模块,你要在C模块开发逻辑,但是需要引入A模块的代码,目前的引入是A引入B和C,B引入C,因为C已经被A引入了,但是C无法引入A,如果强行引入会造成循环依赖,但是假如你要在C拿到A的代码,是无法拿到的,因为没有引入对应模块,无法调用对方的方法感觉有点绕,那么简单的说,就是
我们需要规范好怎么引入模块,才能确保所有模块各司其职,不会出现需要调用到对应的代码但是调用不到的情况
✏️✏️✏️✏️✏️
按照我们的三层框架,我们应该怎么引入?
首先,
config
是配置,应该是一个独立的模块,其他模块可以依赖于它
data
是一个实体类相关的,那么它应该可以被任意的模块调用到
service
依赖于
data
,
dao
,需要进行数据库的调用,才能做到业务相关的逻辑
dao
,因为和数据库相关的进行交互,而数据库连接相关逻辑一般会写在
config
,所以
dao
依赖于
config
web
,是接口调用和启动相关的逻辑,所以依赖于
service
和
config
模块
模块名称模块引入范围data无config无daodata, configservicedata,dao,servicewebconfig,service
✒️✒️✒️✒️✒️
搭建步骤
那么正式开始我们的搭建
- 首先,我们选择新建项目 新建一个MAVEN项目,填写项目名称,组织等
PS: 我的IDEA为2024,可能部分操作有些不一样,但是建立的项目大差不差
初始项目搭建为如下:
接下来把搭建的项目的
src文件夹删掉,只保留pom.xml
然后在该项目,选择新建module
NEW -> Module..
新建config模块
然后把config模块的Main的主入口文件删除
按照如上步骤,把剩下的模块新建出来,同样把Main入口删除,但是
只保留web模块的Main入口文件
最终项目结构如下:
这里为了方便标识,我把web模块的Main修改为WebApplication
然后我们把父模块的pom.xml修改为如下:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><groupId>com.hxc</groupId><artifactId>multipleModuleDemo</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><name>multipleModule</name><description>多module项目demo</description><modules><module>config</module><module>web</module><module>dao</module><module>service</module><module>data</module></modules><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.6.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.6.2</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.8.1</version><!-- 根据需要调整版本 --><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><version>2.6.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.18</version><optional>true</optional></dependency></dependencies></project>
这里我引入了springboot相关的一些必要依赖,后续可根据实际项目情况进行添加,另外父模块的依赖是能够被子模块引入的,有什么必要的也可以引入到父模块的pom,或者新加一个common模块,专门供其他子模块调用
config的pom.xml修改:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hxc</groupId><artifactId>multipleModulesDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>config</artifactId><name>config</name><version>1.0-SNAPSHOT</version><description>配置模块</description></project>
data的pom.xml修改:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hxc</groupId><artifactId>multipleModulesDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>data</artifactId><version>1.0-SNAPSHOT</version><name>data</name><description>数据实体模块</description></project>
dao的pom.xml
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hxc</groupId><artifactId>multipleModulesDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>dao</artifactId><version>1.0-SNAPSHOT</version><name>dao</name><description>数据库</description><dependencies><dependency><groupId>com.hxc</groupId><artifactId>data</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.hxc</groupId><artifactId>config</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency></dependencies></project>
service的pom.xml修改:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hxc</groupId><artifactId>multipleModulesDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>service</artifactId><version>1.0-SNAPSHOT</version><name>service</name><description>逻辑层</description><dependencies><dependency><groupId>com.hxc</groupId><artifactId>dao</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.hxc</groupId><artifactId>data</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.hxc</groupId><artifactId>config</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>
web的pom.xml修改:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.hxc</groupId><artifactId>multipleModulesDemo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>web</artifactId><dependencies><dependency><groupId>com.hxc</groupId><artifactId>config</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.hxc</groupId><artifactId>service</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.6.2</version></dependency></dependencies></project>
那么,为了能够正常的搭建一个可以启用的多模块项目,我这里补充一个逻辑代码,实现查询和新增接口正常调用和项目启动
在数据库新建一个用户表userData,建表语句如下
CREATETABLE userData (
user_id varchar(100)NOTNULLCOMMENT'userId',
nick_name varchar(100)NULLCOMMENT'名称',
sex varchar(10)NULLCOMMENT'性别',
age INTNULLCOMMENT'年龄',CONSTRAINT userData_pk PRIMARYKEY(user_id))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
在data模块新建一个实体UserData
importlombok.Getter;importlombok.Setter;importlombok.ToString;@Getter@Setter@ToStringpublicclassUserData{privateString userId;privateString nickName;privateString sex;privateInteger age;}
在dao模块新建一个jdbc数据库查询代码UserDataDao
importcom.hxc.user.UserData;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.util.ArrayList;importjava.util.List;publicclassUserDataDao{privatestaticfinalStringSQL_INS="INSERT INTO userdata(user_id,nick_name,sex,age) VALUES (?,?,?,?)";privatestaticfinalStringSQL_UPD="UPDATE userdata SET nick_name=?,sex=?,age=? WHERE userid=?";privatestaticfinalStringSQL_SEL="SELECT user_id,nick_name,sex,age FROM userdata ";privatestaticfinalStringSQL_DEL="DELETE FROM userdata WHERE user_id = ?";privatefinalConnection conn;publicUserDataDao(Connection conn){this.conn = conn;}publicintinsert(UserData data){try(PreparedStatement ps =this.conn.prepareStatement(SQL_INS)){
ps.setString(1, data.getUserId());
ps.setString(2, data.getNickName());
ps.setString(3, data.getSex());
ps.setInt(4, data.getAge());return ps.executeUpdate();}catch(SQLException e){thrownewIllegalStateException("数据库查询错误, "+ e.getMessage(), e);}}publicintupdate(UserData data){try(PreparedStatement ps =this.conn.prepareStatement(SQL_UPD)){
ps.setString(1, data.getNickName());
ps.setString(2, data.getSex());
ps.setInt(3, data.getAge());
ps.setString(4, data.getUserId());return ps.executeUpdate();}catch(SQLException e){thrownewIllegalStateException("数据库查询错误, "+ e.getMessage(), e);}}publicintdelete(String userId){try(PreparedStatement ps =this.conn.prepareStatement(SQL_DEL)){
ps.setString(1, userId);return ps.executeUpdate();}catch(SQLException e){thrownewIllegalStateException("数据库查询错误, "+ e.getMessage(), e);}}publicList<UserData>selectAll(){ArrayList<UserData> result =newArrayList<UserData>();try(PreparedStatement ps =this.conn.prepareStatement(SQL_SEL)){ResultSet rs = ps.executeQuery();while(rs.next()){
result.add(convert(rs));}return result;}catch(SQLException e){thrownewIllegalStateException("数据库查询错误, "+ e.getMessage(), e);}}publicUserDataselectByUserId(String userId)throwsSQLException{UserData result =null;try(PreparedStatement ps = conn.prepareStatement(SQL_SEL+"WHERE user_id = ?")){
ps.setString(1, userId);ResultSet rs = ps.executeQuery();if(rs.next()){
result =convert(rs);}return result;}catch(SQLException e){thrownewSQLException(e.getMessage());}}privateUserDataconvert(ResultSet rs)throwsSQLException{UserData data =newUserData();int index =1;
data.setUserId(rs.getString(index++));
data.setNickName(rs.getString(index++));
data.setSex(rs.getString(index++));
data.setAge(rs.getInt(index++));return data;}}
在config模块新建一个数据库连接配置DataSourceConfig,和数据库连接实现类PrimeDB
importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.jdbc.datasource.DriverManagerDataSource;/**
* 数据库配置
* */@ConfigurationpublicclassDataSourceConfig{@Value("${spring.datasource.prime-data.driver-class-name}")privateString primeDataDriver;@Value("${spring.datasource.prime-data.url}")privateString primeDataUrl;@Value("${spring.datasource.prime-data.username}")privateString primeDataUsername;@Value("${spring.datasource.prime-data.password}")privateString primeDataPassword;@BeanpublicDriverManagerDataSourceprimeDataSource(){DriverManagerDataSource dataSource =newDriverManagerDataSource();
dataSource.setDriverClassName(primeDataDriver);
dataSource.setUrl(primeDataUrl);
dataSource.setUsername(primeDataUsername);
dataSource.setPassword(primeDataPassword);return dataSource;}// 配置其他数据库连接(待定占位)@BeanpublicDriverManagerDataSourceotherDataSource(){DriverManagerDataSource dataSource =newDriverManagerDataSource();
dataSource.setDriverClassName(primeDataDriver);
dataSource.setUrl(primeDataUrl);
dataSource.setUsername(primeDataUsername);
dataSource.setPassword(primeDataPassword);return dataSource;}}
importjava.sql.Connection;importjava.sql.SQLException;importjavax.sql.DataSource;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.stereotype.Component;@ComponentpublicclassPrimeDB{privatefinalDataSource primeDataSource;@AutowiredpublicPrimeDB(@Qualifier("primeDataSource")DataSource primeDataSource){this.primeDataSource = primeDataSource;}publicConnectioncreate()throwsSQLException{Connection connection =null;try{
connection = primeDataSource.getConnection();return connection;}catch(SQLException e){// 处理连接获取失败的异常情况thrownewRuntimeException("连接数据库失败:", e);}}}
那么为了可以实现测试类正常调用,可在config的resource下建立一个application.yml,为数据库连接配置,但是为了正常启用项目,需要在web模块的resource新建一个application.yml
如下为config的resource下建立一个application.yml
spring:application:name: multipleModuleBack
datasource:prime-data:type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/primeData?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true&useCompression=false&useSSL=falseusername: root
password: root
在我们的service模块,新建一个UserService
importcom.hxc.dao.UserDataDao;importcom.hxc.user.UserData;importjava.sql.Connection;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;@ServicepublicclassUserService{publicUserDatafindUser(Connection conn,String userId)throwsException{try{UserDataDao userDataDao =newUserDataDao(conn);UserData userData = userDataDao.selectByUserId(userId);if(null== userData){thrownewException("查无用户:"+ userId);}return userData;}catch(Exception e){thrownewException("获取用户失败:"+ e);}}publicStringinsertUser(Connection conn,UserData userData)throwsException{try{UserDataDao userDataDao =newUserDataDao(conn);if(!StringUtils.hasText(userData.getUserId())){thrownewException("用户id不能为空!");}if(!StringUtils.hasText(userData.getNickName())){thrownewException("用户名称不能为空!");}UserData record = userDataDao.selectByUserId(userData.getUserId());if(null!= record){thrownewException("用户:"+ userData.getUserId()+"已存在");}
userDataDao.insert(userData);return"新增成功!";}catch(Exception e){thrownewException("新增用户失败:"+ e);}}}
在web模块下建立一个接口调用文件UserWebServicer
importcom.hxc.configs.db.PrimeDB;importcom.hxc.user.UserData;importjava.sql.Connection;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")publicclassUserWebServicer{@AutowiredprivatePrimeDB primeDB;@GetMapping("/findUser")publicUserDatafindUser(@RequestParam("userId")String userId)throwsException{System.out.println("Received request with userId: "+ userId);try(Connection conn = primeDB.create()){returnnewUserService().findUser(conn, userId);}catch(Exception e){thrownewException(e);}}@PostMapping("/insertUser")publicStringsendMessage(@RequestBodyUserData userData)throwsException{try(Connection conn = primeDB.create()){returnnewUserService().insertUser(conn, userData);}catch(Exception e){thrownewException(e);}}}
同时把webApplication的代码修改为:
importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassWebApplication{publicstaticvoidmain(String[] args){System.out.println("多模块项目启动主入口");SpringApplication.run(WebApplication.class, args);}}
然后在web模块的resource下新建一个application.yml
server:port:8082servlet:context-path: /multipleModule
spring:application:name: multipleModuleBack
datasource:prime-data:type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/primeData?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true&useCompression=false&useSSL=falseusername: root
password: root
这个时候我们就可以右键WebApplication启动我们的项目了
现在我的数据该表的数据有如下:
调用我的查询接口结果如下:
我们的项目正常运行
重要信息
以此,我们的项目就正式搭建好了,后续我们这个项目的调用情况是在data模块进行建立实体,我们的dao模块调用data模块的实体,和config的数据库连接进行数据库的增删改查,然后我们的service模块进行业务逻辑的开发,在web编写接口调用我们的业务逻辑,按照这个顺序执行就能够正常的运行我们的项目,不会出现循环依赖问题,后续如果有新加别的模块也是按照这个思路进行添加
git项目demo
如下为本次教学的项目demo链接,可进行参考
SPRINGBOOT 多模块项目DEMO
结语
以上为springboot搭建多模块的方法和步骤,如有遗漏将在本文章补充
版权归原作者 相与还 所有, 如有侵权,请联系我们删除。