1、前言
1.1 初衷
本来接下来应该是实战一个登陆页面的,但是感觉每个实战应该可以分开来,这样有个循序渐进的过程,我想的是整合成类似下面的形式:
一个项目中有多个模块,每个模块负责不同的实战内容,相互独立,也可以相互依赖。这样以后复习起来也会很有针对性。
本来以为很简单的事情,结果发现其中涉及内容很多,这里就单独作为一章来仔细讲一讲。
注意:多模块是 maven3 以上才支持,也叫作聚合项目
1.2 idea 中,project 与 module 的区别
idea 中,project 的概念:
IntelliJ IDEA Project 代表一个完整的软件解决方案,是开发过程中的一个顶层的组织单元,一个 IntelliJ IDEA 窗口只能显示一个 Project。
idea 中,module 概念:
一个 Project 可以包含一个或多个 Module,一个 Module 是软件解决方案的一部分,可以独立地编译、运行、测试以及调试,Module 有利于降低大型工程复杂度,譬如将通用的配置提成一个单独的 Module 进行维护。Module 也可以复用于多个工程中。
两者的关系可以参照 eclipse 的体系:
2、创建 maven 多模块项目
我发现使用 spring initializr 创建子模块和使用 maven 方式创建子模块导致的结果不一样,当然父模块创建并无太大区别,因为父模块只是用作依赖管理,并没有代码,所以父模块使用哪种方式都可以(下面讲解中,我将使用 maven 方式创建父模块)。
注意:本文中父模块是指上面的 project 概念,并不是指一个 module,具体我会在最后在进行详细说明。
下面我分两种情况来介绍(当前 idea 版本为 2019 版)。
2.1 使用 maven 创建子模块
第一步:创建一个 maven 项目作为父模块
首先要创建一个 maven 项目,其作用是作为父模块管理其他子模块,只作为依赖管理。
打开 idea,file——new——project:
这里选择 maven,因为不需要进行依赖下载,也没有其他那么多选项需要设置,用 maven 创建项目比较干净,同时不需要联网下载模板创建速度会快很多。
如上图所示,只需更改上面三个红框地方即可,其中项目(父模块)命名为 springboot-demo,这里说一下 spring 项目名称的命名规范:
项目名使用小写英文单词,多个之间使用连字符 “-” ,例如 my-test。
包命名规范:
com.company.{项目英文名(较长时适当简化)}.{模块名(可选)}
例如:test 包命名为 com.dhsg.test。
注意:包命名全为小写英文字母,不可以使用大写英文字母,不可以使用 “_”、"-" 等符号
OK,整完之后,界面如下:
没有加任何依赖,没有任何项目代码,看起来非常简洁干净。
将 src 文件目录删除掉,然后在 pom 文件中加入:
<packaging>pom</packaging>
OK,父模块创建成功了,其完整页面如下:
问题一:为什么要删除 src 目录
父模块本身是不运行程序的,其充当一个目录包的作用,而 src 是代码资源目录,对于父项目来说是多余的,所以要删除(当然,如果你想留着也是可以的,并不影响,删除它只是约定俗成的习惯罢了)。
问题二:pom是什么意思?
父项目的 pom 文件完整配置应该是:
<packaging>pom</packaging>
<modules>
<!-- 模块都写在此处 -->
<module>子模块名1</module>
<module>子模块名2</module>
....
</modules>
第一行pom表示该项目的打包方法,如果不添加这一句则默认为 jar 类型,因为父项目本身不含有任何 java 代码,所以不需要打包(打完包也无法运行啊)。
打包为 pom 方式,其作用仅仅是一个引用其它子模块(modules 里罗列出来的)打包作用,即将其它子模块按照各自的 pom 文件都进行打包。
顺便说一下 packing 其他类型:
- pom:父类型都为 pom 类型
- jar:内部调用或者是作服务使用
- war:需要部署的项目
注意:这行代码一定要写的,不然就会将整个父模块都打包(默认打包模式为 jar)。
第二步:创建一个子模块——demo-admin
选中父模块 springboot-demo,右键:new——module:
依然使用 maven 创建模块。
可以看到这里创建子模块的时候,第一行自动出现父模块或者选项,这个是默认的,不用更改,下面的一些参数比如 groupid 都是继承自父模块,只需要更改 name 即可。
创建完成后,目录以及 pom 文件的更改:
可以看到父模块的 pom 文件发生了变化,添加如下:
<modules>
<module>demo-admin</module>
</modules>
这个表明 demo-admin 为其子模块,而新创建的子模块 demo-admin 比父模块要多出一块来:
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>org.lanya</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
这个表明其父模块为 springboot-demo,结合上面的 modules,就为 springboot-demo 和 demo-admin 之间创建了双向的父子联系。
问题一:这个联系有什么作用呢?
这个联系代表了继承关系,可以让子模块继承父模块的一些参数和依赖,只要在父模块中添加依赖,子模块就可以调用父模块的依赖。
另外,当存在多个子模块的时候,父模块可以为多模子模块打包,打包原理就是利用 pom 打包方式依次调用各个子模块(modules 中罗列出来的模块)的 pom 文件,将各个子模块打包。
第三步:创建新的子模块——demo-common
和上一步一样,new 一个新的 module,依然选择 maven 方式创建子模块,父模块为 springboot-demo,只是子模块命名进行一下修改即可,这里不再贴图,最终形成的目录如下:
然后,我们再看一下三个模块的 pom 文件变化:
可以看到新创建的 demo-common 模块和子模块 demo-admin 是一模一样的,而父模块 springboot-demo 的 pom 文件则发生了变化——modules 中多了一个模块:
<modules>
<module>demo-admin</module>
<module>demo-common</module>
</modules>
第四步:将父模块与子模块之间的继承关系说明
父模块与子模块的关系:
父模块,即 springboot-demo 用于依赖管理,说白了其主要文件就是 pom.xml,里面可以添加大量的依赖包(dependency),然后子模块如 demo-admin 或者 demo-common 都可以继承这些依赖包,而不用再在自己的 pom 文件中添加这些依赖,你可以将 springboot-demo 中的依赖看做是公共依赖,任何子模块都可以使用(只要声明是 springboot-demo 的子模块即可)
子模块与子模块之间的关系:
子模块与子模块之间可以相互调用对方的类或者函数,前提是要将对方模块作为一个依赖包进行引入到自身的 pom 文件中,一般来说,各个子模块创建完成后,都会先写入其他子模块
OK,明白上面的关系,继承关系需要做的事情就很明白了:
- 第一条:父模块 springboot-demo 要标明它有哪些子模块
- 第二条:各个子模块要标明其父模块是哪个?
- 第三条:各个子模块相互之间要调用则需要引用对方作为依赖包
其中第一和第二条都已经在创建模块的时候自动实现(就是在第二步说明的建立父子联系),我们只需要在实现第三条即可。
第五步:测试继承依赖
实现第三条之前,我们先测试一下看看子模块是否能够继承父模块的依赖。
首先在父模块添加依赖,主要是两块:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
添加位置如下:
OK,父模块添加完了,接下来在两个子模块中添加运行 spring 启动类(这两个启动类都需要调用父模块添加的依赖包,如果能够调用说明确实继承了父模块的依赖包):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoAdminApplication {
public static void main(String[] args) {
SpringApplication.run(DemoAdminApplication.class, args);
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoCommonApplication {
public static void main(String[] args) {
SpringApplication.run(DemoCommonApplication.class, args);
}
}
添加位置:
然后运行两个 spring 启动类,运行成功如下图,则说明确实是继承了父类依赖:
第六步:模块之间添加依赖并进行测试
我们只需要实现第三条——在一个模块中添加另一个模块作为依赖包。
如果子模块 demo-admin 想要调用 demo-common 里面的函数,则要在 demo-admin 的 pom 文件中添加依赖:
<dependencies>
<dependency>
<groupId>org.lanya</groupId>
<artifactId>demo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
添加完,接下来我们添加两个程序,测试一下看是否能进行调用,测试原理:在 demo-common 模块中添加一个获取时间的函数,然后在 demo-admin 中进行调用,看是否能够成功。
首先,添加两个方法类:
import com.lanya.api.GetDate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
@RestController
@RequestMapping("/test")
public class TransferGetDate {
/**
* 测试调用demo-common中的获取时间函数是否生效
*/
@GetMapping("/date")
public String getdate(){
Date d = GetDate.getDate();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return "当前时间:"+df.format(d);
}
}
import java.util.Date;
public class GetDate {
public static Date getDate(){
Date date = new Date();
return date;
}
}
添加位置如下:
OK,让我们看一下调用是否成功,运行 DemoAdminApplication,打开浏览器输入:localhost:8080/test/date,如果得到以下界面,则表示成功调用:
2.2 使用 spring initializr 方式创建子模块
和上面步骤一样,下面一步步来,其中有相似的步骤,比如测试继承依赖和模块间相互调用,就不再写了,这里只讲述两种方式创建子模块的不同。
第一步:使用 spring initializr 方式创建父模块
大方向是不变的,file——new——project:
这一次选择 spring initializr 方式创建,默认模板,next:
按照需求更改名称,这里要注意模块和包的命名规则(上面有,不再赘述)。
依赖都不需要进行勾选:
OK,完成了,看一下目录和 pom 文件:
如上图所示,和采用 maven 创建项目有了很大的区别,上图中红框标出的部分都是比 maven 初始目录多出来的内容,明面上我们关心的,多出来了 spring 启动类还有.properties 文件——也就是 yaml 文件,其中 pom 文件里面的内容多出来部分主要是多了一个父模块——spring-boot-starter-parent,两个依赖以及一个插件,这些内容是因为启动类需要。
这些多出来的内容,其实就是上一步用 maven 创建模块时,我们加入在父模块中的内容。
转回正题,开始将这个模块改为父模块:删除 src 目录,加入:
<packaging>pom</packaging>
第二步:创建子模块——demo-admin
选中 spring-demo1,new——module:
依然选择 spring initializr 来创建。
继续上面的步骤,接下来你就会发现,和第一步是一样一样的。
哎,不对啊,我们在使用 maven 创建子模块的时候,是有绑定父模块的步骤的,为什么在使用 spring initializr 来创建的时候没有呢?
别着急,我们来看一下生成的模块 demo-admin 的 pom 文件:
如上图所示,这个 pom 文件内容和上一步的父模块的 pom 文件内容一模一样(除了模块名称,别较真哈),那么问题来了,这个新生成的模块居然有个父模块,而且这个父模块和 spring-demo1 的父模块一模一样,这个就是为什么没有提示其父模块的设置。
原因在于,我们在使用 spring initializr 创建模块的时候,使用的是线上的模板,这个模板默认了父模块。
OK,这就是我想说的一点,也是使用 spring initializr 创建子模块和使用 maven 创建子模块的区别。
接下来要更改其父模块指向,用下面的代码替换 demo-admin 中的父模块指向即可:
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>org.lanya</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
然后,在父模块中添加指向 demo-admin 为子模块的代码:
<modules>
<module>demo-admin</module>
</modules>
因为继承的关系,想要删除 demo-admin 的依赖也是可以的。
OK,接下来继续创建子模块就没有必要说了,按照上面的做即可。
2.3 究竟用什么方法创建模块好呢?
这个就因目的而异了,两种方式都各有优劣:
- 采用 spring initializr 方式:可以直接获得运行启动类,也能通过勾选的方式添加依赖,方便省事;但是这样无法自动关联父模块和子模块,需要手动添加。
- 采用 maven 的方式:可以自动关联父模块和子模块,不必手动添加;但是需要手动添加依赖和启动类
对于我个人而言,我喜欢混合着创建,比如父类使用 spring initializr,而子类使用 maven 方式创建。当然,有时候其实不需要继承依赖关系的话,就子模块使用 spring initializr,看个人喜欢了。
记住三条关键点即可:
- 第一条:父模块 springboot-demo 要标明它有哪些子模块
- 第二条:各个子模块要标明其父模块是哪个?
- 第三条:各个子模块相互之间要调用则需要引用对方作为依赖包
3、后言
整这个有些背离我最初的目的——我只是单纯想要将多个模块整合在一个目录中,能够相互独立运行即可,并没有想什么继承。
谁知越整越多,多个模块想要出现在一个项目中,在 idea 中只能使采用上面的聚合项目,不像 eclipse 中一个界面可以共存多个项目。
说多了,说一下聚合项目的优势:
- 方便管理
- 项目可以进行纵向(分为多个模块)和横向的切分(每个模块分成了 web 层,service 层、dao 层),从而避免一个人开发的尴尬,便于团队开发,提高开发效率
- 项目整合:横向拆分后,每个功能模块进行了单独的开发之,项目整合的时候只需要有一个能够整合这些项目或者模块的工程即可
- 因为有继承,方便复用,减少工作量,同时使得项目更加安全
- 防止 pom 变得过于庞大
OK,今天就到这里,更多精彩内容关注我的个人网站:蓝亚之舟博客。