多Module
什么是多Module
多模块项目指的是包含多个子模块(Module)的项目,在多模块项目中,父项目只作为一个子项目的容器而存在,不包含具体的代码。
为什么要用多Module
多Module本质上是一种封装,做一些解耦的工作。我觉得可以用在:
目前看,我觉得比较不错的分组方式是:
| - parent
| - common
| - api
| - service
这种适合api分离的场景
或者:
| - parent
| - common
| - biz
这种api不分离的场景
如何使用多Module
参考、参考
-
父POM
这里需要主要这几个配置:
packaging:打包类型,可选选择jar、war、pom,父POM是pom类型,默认是jar,目前springboot中war用处减少了。
modules:声明子模块
dependencies:将全部的依赖都定义在父POM中,所有子模块都使用相同这些依赖,这样做的好处是子POM中的依赖就比较少了,坏处时不管该子POM需不需要,父POM中的依赖就会存在该子模块中。
dependencyManagement:全部的依赖都声明在父POM中,主要管理这些POM的版本。子POM中再次声明时,就不用定义版本了,这样做到了版本一致。并且只有当子POM声明时,才会被引入到该子模块中。
这样将dependencies与dependencyManagement连用,全部dependency的版本用dependencyManagement来管理,公共的dependency通过dependencies来声明。
<!-- 基本信息 -->
<description>SpringBoot 多模块构建</description>
<modelVersion>4.0.0</modelVersion>
<name>spring-boot-multi-module</name>
<packaging>pom</packaging>
<!-- 项目说明:这里作为聚合工程的父工程 -->
<groupId>com.hehe</groupId>
<artifactId>spring-boot-multi-module</artifactId>
<version>1.0.0.RELEASE</version>
<!-- 继承说明:这里继承SpringBoot提供的父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/>
</parent>
<!-- 模块说明:这里声明多个子模块 -->
<modules>
<module>mm-commont</module>
<module>mm-service</module>
<module>mm-api</module>
</modules>
<!-- 公共依赖-->
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
</dependencies>
<!-- 版本说明:这里统一管理依赖的版本号 -->
<<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
父级中,只留pom.xml与.gitignore即可,其他的.mvn、src等等都可以删除掉
-
子Module
在父工程中增加新的Module,pom需要注意:
parent: 声明parent项目
build: 在可执行的jar上声明,构建可执行的JAR
<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">
<modelVersion>4.0.0</modelVersion>
<!-- 基本信息 -->
<groupId>com.hehe</groupId>
<artifactId>mm-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mm-web</name>
<!-- 继承本项目的父工程 -->
<parent>
<groupId>com.hehe</groupId>
<artifactId>springboot-integration</artifactId>
<version>1.0.0.RELEASE</version>
</parent>
<!-- Web模块相关依赖 -->
<dependencies>
<dependency>
<groupId>com.hehe</groupId>
<artifactId>mm-service</artifactId>
</dependency>
<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>
<build>
<plugins>
<plugin>
<!--该插件主要用途:构建可执行的JAR -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Maven
这里对maven进行一个梳理,参考
简介
maven是java平台的自动化构建工具。这里的构建指的是把Web项目经过编译得到的编译结果,然后部署到服务器上的整个过程。
其各个周期包括:
-
清理clean:将以前编译得到的旧文件class字节码文件删除
mvn clean
-
编译compile:将java源程序编译成class字节码文件
mvn compile
-
测试test:自动测试,自动调用junit程序
mvn test-compile 与 mvn test
-
报告report:测试程序执行的结果
-
打包package:动态Web工程打War包,java工程打jar包
mvn package
-
安装install:将将包安装至本地仓库,以让其它项目依赖
mvn install
-
部署deploy:将动态Web工程生成的war包复制到Servlet容器下,使其可以运行
仓库和坐标
-
pom.xml 是maven核心配置文件
-
坐标
坐标指的是GAV:groupId-artifactId-version
-
仓库
仓库有2种:
本地仓库:$HOME/.m2/repository
远程仓库:私服,搭建在局域网中,一般公司都会有私服,私服一般使用nexus来搭建。
中央仓库,架设在Internet上,像刚才的springframework就是在中央仓库上
依赖
- maven解析依赖信息时会到本地仓库中取查找被依赖的jar包
对于本地仓库中没有的会去中央仓库去查找maven坐标来获取jar包,获取到jar之后会下载到本地仓库,如果没有则编译失败
-
如果依赖的是自己或者团队开发的maven工程,需要先使用install命令把被依赖的maven工程的jar包导入到本地仓库中
现在我再创建第二个maven工程HelloFriend,其中用到了第一个Hello工程里类的sayHello(String name)方法。我们在给HelloFriend项目使用 mvn compile命令进行编译的时候,会提示缺少依赖Hello的jar包。怎么办呢?
到第一个maven工程中执行 mvn install后,你再去看一下本地仓库,你会发现有了Hello项目的jar包。一旦本地仓库有了依赖的maven工程的jar包后,你再到HelloFriend项目中使用 mvn compile命令的时候,可以成功编译
示例
WebMavenDemo项目依赖JavaMavenService1,JavaMavenService1项目依赖JavaMavenService2
pom.xml文件配置好依赖关系后,必须首先mvn install后,依赖的jar包才能使用。
- WebMavenDemo的pom.xml文件想能编译通过,JavaMavenService1必须mvn install
- JavaMavenService的pom.xml文件想能编译通过,JavaMavenService2必须mvn install
-
依赖范围
**1、compile,**默认值,适用于所有阶段(开发、测试、部署、运行),本jar会一直存在所有阶段。
**2、provided,**只在开发、测试阶段使用,目的是不让Servlet容器和你本地仓库的jar包冲突 。如servlet.jar。
**3、runtime,**只在运行时使用,如JDBC驱动,适用运行和测试阶段。
**4、test,**只在测试时使用,用于编译和运行测试代码。不会随项目发布。
**5、system,**类似provided,需要显式提供包含依赖的jar,Maven不会在Repository中查找它。
-
工具
通过 mvn dependency:tree
IDEA Maven Helper插件
-
排除特定依赖
exclusion
生命周期
Maven有三套相互独立的生命周期,
-
Clean Lifecycle 在进行真正的构建之前进行一些清理工作
-
Default Lifecycle 构建的核心部分,编译,测试,打包,部署等等。
-
Site Lifecycle 生成项目报告,站点,发布站点
build插件
<build>
<plugins>
<plugin>
<!--该插件主要用途:构建可执行的JAR -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
前后端分离部署
这里主要是前后端分离情况下的部署问题,有如下3种部署方式:
-
前端部署在jar中
将前端打包的js、css、html等文件,拷贝到SpringBoot项目的src/main/resources/static目录下
-
前端直接部署在nginx下
将前端打包的文件,通过nginx静态文件代理的方式来访问
location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff|html|txt|pdf|) {
root /usr/local/nginx/html/; #所有静态文件直接读取硬盘
expires 30d; #缓存30天
}
-
专门的前端服务,部署在nginx后
这种可以采用Node来做模板渲染,又分成2种情况:
- 浏览器渲染方式,则Node只渲染一个空html模板,页面内容完全由JS生成。
- 同构渲染方式,让Node渲染模板时可以直接使用Vue和React的同构组件,直出页面后,用户的交互体验又如单页应用般流畅镜像。
这里简单对比这3种方案:
第一种方案最简单,甚至不需要nginx。问题在于前端如果修改、部署,那jar就需要重新部署,前后端有耦合
第二种方案通过nginx来代理,让前端脱离了后端部署,一定程度上做了解耦。但它又增加了nginx与前端的耦合,如果是采用docker方式,前端打包的要么打包进nginx,要么通过mount的方式映射本地磁盘,显然后者更好一些。但这种方式在K8S方式下,就显得不那么好了。
第三种方案真正意义上解耦了前后端,而且不同于前两者的前端渲染(CSR)方式,它可以既提供前端渲染,又提供后端渲染。的确是最好的方式。
目前情况下我们在主nginx下,前端服务又用nginx做了一个静态服务器来做,这样做暂时也可以,属于第三种方案,但没有SSR的第3种方案。
参考1
参考2
搭建Docker镜像
这里简单区分一下Dockerfile中RUN、CMD、ENTTRYPOINT
命令 | 作用 | 格式 |
RUN | 运行安装命令 | RUN ["executable", "param1", "param2"] |
CMD | 容器启动时的命令 | CMD["executable", "param1", "param2"] |
ENTRYPOINT | 容器启动后的命令 | ENTRYPOINT ["executable", "param1", "param2"] |
dockerfile-maven-plugin插件
pom的build中增加该插件,如下:
<project>
<properties>
<java.version>1.8</java.version>
<docker.image.prefix>springbucks</docker.image.prefix>
</properties>
<!--略-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
docker.image.prefix是自定义的参数
project.actifactId、project.version等都是pom文件中的属性
project.build.finalName是build之后的结果
注意:这里的JAR_FILE参数,后边的dockerfile中会用到
dockerfile
这个的JAR_FILE为上文的参数
FROM java:8
EXPOSE 8080
ARG JAR_FILE
ADD target/${JAR_FILE} /myservice.jar
ENTRYPOINT ["java", "-jar","/myservice.jar"]
这里相当于docker build --build-arg JAR_FILE=...
执行构建
执行打包命令:
mvn clean package -Dmaven.test.skip=true
上传大约可以用:
mvn dockerfile:push