# 构建
- 简单的 Java 项目只包含少量源文件,可用 javac 命令手动编译。
- 复杂的 Java 项目包含大量源文件、配置文件、依赖库等,手动编译很麻烦,因此通常用 Ant、Maven、Gradle 等工具自动地构建(build),包括编译(compile)、测试(test)、打包(package)等步骤。
- 构建 Java 项目时,通常将编译生成的类文件,与配置文件、依赖 jar 等一起,打包成一个 ZIP 格式的压缩包,扩展名为 .jar 。
- 构建 Java Web 项目时,通常打包的扩展名为 .war 。
- jar 或 war 包是一个独立的 Java 程序,可以被 java 命令直接运行。
# 编译
Java 代码需要先编译才能运行,基本的编译流程如下:
- 安装 JDK 。
- 编写一个源文件 Hello.java :
public class Hello { public static void main(String[] args) { System.out.println("Hello world"); } }
- 用 javac 命令编译源文件,生成字节码,存储在 .class 类文件中。
[root@CentOS ~]# ls Hello.java [root@CentOS ~]# javac Hello.java # 这会编译生成一个类文件 Hello.class [root@CentOS ~]# ls Hello.class Hello.java
- 用 java 命令运行 .class 类文件:
[root@CentOS ~]# java Hello Hello world [root@CentOS ~]# java Hello.class # 运行类文件时不需要输入扩展名 .class ,否则会被当做类名的一部分 Error: Could not find or load main class Hello.class
字节码(Byte-code)
- :一种二进制代码,是介于编程语言代码与机器代码之间的中间代码。
类文件
- :一种扩展名为 .class 的文件,用于存储 .java 源文件编译生成的字节码,因此又称为字节码文件。
# JVM
JVM
- :Java 虚拟机(Java Virtual Machine),是一个负责运行 class 文件的程序。
- 编译生成的 class 文件,其内容是字节码,不是机器代码,不能被计算机直接运行,只能被 JVM 程序运行。
- 用 java 命令运行 .class 文件时,原理是启动一个 JVM 进程,读取 class 文件中的字节码,编译成本机的机器代码,然后执行。
- 官方每隔几个月会制定新版本的 Java 标准,包含新的语法、功能,并发布相应的 JVM 。因此,如果用户编写的 Java 代码,使用了最新的语法特性,则使用最新版本的 JVM 才能运行。
Java 编程语言最初的卖点是,跨平台,用户编写了一个 Java 程序之后,不需要修改源代码,就可以移植到不同平台上运行。
- 为了实现这一卖点,官方为不同机器平台、操作系统分别研发了一个 JVM 程序,这些 JVM 向下调用各个平台的底层接口,向上提供相同的 JVM 接口。
- 用户编译出一个 class 文件之后,可以拷贝到不同平台的 JVM 上运行,使得用户不必考虑这些平台的底层差异。
- 因此,一般开发 Java 程序时,需要先将 .java 源文件编译成 .class 文件,然后让 JVM 转换成机器代码、执行。
- 如果用户将 .java 源文件直接编译成机器代码,则优点是可以直接用作可执行文件,不用安装 JVM 。缺点是不能移植到不同平台上运行。
有多家公司研发了不同的 JVM 程序。目前最常用的一款 JVM 程序,是 HotSpot 。
- HotSpot 采用 C++ 语言开发。
- HotSpot 采用即时编译(Just In Time,JIT)技术。将字节码转换成机器代码时,既会解释,又会编译。
- 它会一边读取字节码,一边解释执行。并且找出经常被执行的某些字节码,称为热点(hot spot),将它们编译成机器码再执行,从而提高效率。
- 为什么不将所有字节码都解释执行?因为解释时,启动速度快(因为可以更快地得到机器代码),但运行速度慢(因为得到的机器代码存在很多优化空间)。
- 为什么不将所有字节码都编译执行?因为编译时,启动速度慢(因为编译耗时久),但运行速度快(因为编译得到的机器代码,经过了很多优化)。
# JDK
JDK
- :Java 开发环境工具包(Java SE Development Kit),包含 JRE 以及开发、调试、监控工具。
JRE
- :Java 运行环境工具包 (Java Runtime Environment),只提供 JVM 、类库。
- JRE 的体积比 JDK 小,可以节省磁盘空间。
存在哪些 JDK ?
- 目前 JDK 的官方标准实现是 OpenJDK ,由开源社区维护。
- 有多家公司基于 OpenJDK 源代码,制作了自己的 JDK 二进制发行版。例如:
- Oracle JDK :由 Oracle 公司发布,从 2019 年开始收费。
- Temurin JDK :由 Eclipse 基金会发布,开源。
- 2001 年,IBM 公司发布了一个名为 Eclipse 的 IDE ,主要用于 Java 开发。
- 为了与微软公司的 Visual Studio IDE 竞争,IBM 公司开源了 Eclipse ,并成立了 Eclipse 开源软件基金会。
# 安装
用 yum 或 apt 安装 JDK :
yum install java-1.8.0-openjdk-devel
apt install openjdk-8-jdk
或者从官网 (opens new window)下载二进制版:
wget http://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb4ef291e347e091f7f4a7/jdk-8u141-linux-x64.tar.gz --header "Cookie: oraclelicense=accept-securebackup-cookie" tar -zxvf jdk-8u141-linux-x64.tar.gz -C /usr/local/ # 配置环境变量,从而能定位 java 命令 echo 'export JAVA_HOME=/usr/local/jdk1.8.0_141' >> /etc/profile echo 'export PATH=$PATH:$JAVA_HOME/bin' >> /etc/profile source /etc/profile
或者用 Docker 部署:
docker run -it --rm openjdk:8-jdk java
2022 年,Docker Hub 宣布停止维护 openjdk 镜像,用户可以改用 Temurin JDK :
docker run -it --rm eclipse-temurin:17-jdk-jammy java
# 命令
# javac
javac
*.java # 编译源文件
-sourcepath <path> # 查找源文件的路径
-classpath <path> # 用于查找类库的路径。该配置会覆盖环境变量 CLASSPATH
-d . # 编译出的类文件的保存路径
-encoding utf-8 # 源文件的编码格式
- path 默认为当前目录。如果指定多个路径,在 Winodws 上用 ; 分隔,在 Linux 上用 : 分隔。
# java
java
<class name> # 运行类文件(在前台运行)
-jar xx.jar # 运行 jar 包
-jar xx.war --httpPort=8080 # 运行 war 包
-classpath <path> # 用于查找类库(通常保存为 jar 包)的路径。该配置会覆盖环境变量 CLASSPATH
-cp <path> # 等价于 -classpath 选项
-D <property>=<value> # 配置一个属性的值。可以多次使用该选项。可用 System.out.println(System.getProperty("user.timezone")); 查看一个属性的值
user.timezone=GMT+08 # 时区
java.io.tmpdir=/tmp # 临时目录,供 JVM 创建、保存临时文件
-version # 打印 JDK 或 JRE 的版本信息
-verbose jar # 打印所有 jar 依赖包的文件路径
-XshowSettings:properties -version # 打印所有系统属性,例如 java.runtime.version
-XX:+PrintCommandLineFlags # 打印当前生效的 java 命令行参数,例如 -XX:MaxHeapSize 容量、采用哪种 GC 垃圾收集器
-XX:+PrintFlagsFinal # 打印当前所有 flag 配置参数的取值。每个参数原本会显示默认值,但如果用户修改了该参数的值,则显示的是当前生效的值
# 关于 Heap 内存
-Xms?? # -XX:InitialHeapSize ,堆内存的初始容量(并不是最小容量),默认为主机 RAM 的 1/64 。可配置 -Xmx100M、-Xms1G 等单位
-Xmx?? # -XX:MaxHeapSize ,堆内存的最大容量,默认为主机 RAM 的 1/4
-Xmn?? # 堆内存中 young 区域的容量,剩下的堆内存则属于 old 区域。配置了该参数时,会自动配置 -XX:NewSize 和 -XX:MaxNewSize
-XX:NewSize=?? # young 区域的初始容量
-XX:MaxNewSize=?? # young 区域的最大容量
-XX:NewRatio=2 # 堆内存中,old 区域容量是 young 的多少倍,默认为 2 ,即让 -Xmn 等于 -Xmx/(2+1)
-XX:SurvivorRatio=8 # young 区域中,eden space 容量是单个 survivor space 容量的多少倍,默认为 8 。默认有 2 个 survivor space ,因此每个的容量为 NewSize/(8+2)
-XX:+UseAdaptiveSizePolicy # 启用自适应大小策略,每次 GC 后,会根据 GC 性能指标自动调整 eden space、survivor space 容量的比例。采用 ParallelGC 时默认启用该功能,但显式配置 SurvivorRatio 时该功能不生效
-XX:InitialTenuringThreshold=7
-XX:MaxTenuringThreshold=15 # TenuringThreshold 的最大值
# 关于 Metaspace 内存。一般不需要配置,因为一般的 Java 程序只占用几十 MB
-XX:MetaspaceSize=20.8M # Metaspace 的初始容量,默认为 20.8M
-XX:MaxMetaspaceSize=?? # Metaspace 的最大容量,默认不限制
# 当 Metaspace 当前容量写满时,会触发 full GC 。如果 full GC 之后依然内存不足,则会增加当前容量
# 如果 full GC 之后想增加当前容量,却达到 MaxMetaspaceSize 限制,则会抛出 OutOfMemoryError 错误
# 如果决定配置 MaxMetaspaceSize ,则建议将 MetaspaceSize 配置为相同的值,避免多次扩容之前的 full GC
-XX:MinMetaspaceFreeRatio=40 # 最小空闲率。如果 full GC 之后,Metaspace 中空闲内存所占百分比小于该值,则认为空闲内存太少,于是扩容 MetaspaceSize
-XX:MaxMetaspaceFreeRatio=70 # 最大空闲率。如果 full GC 之后,Metaspace 中空闲内存所占百分比大于该值,则认为空闲内存太多,于是减小 MetaspaceSize
# 关于其它内存
-XX:MaxDirectMemorySize=?? # Direct Memory 的最大容量,默认等于 -Xmx 。一般不需要配置,因为一般的 Java 程序只占用几十 MB
-Xss1M # 每个线程的堆栈大小,默认为 1M
# Java 8u191 新增了几个配置参数,用于适配容器
-XX:+UseContainerSupport # 该功能默认启用。用于在容器中运行 JVM 时,自动根据 Cgroup 的 cpu、memory 限额,配置 -XX:ActiveProcessorCount、-Xmx 的默认值
-XX:ActiveProcessorCount=<int> # 配置 JVM 可使用的 CPU 核数
-XX:InitialRAMPercentage=1.5625 # 如果未显式配置 -Xms ,则将 Cgroup memory 限额的该百分比赋值给 -Xms (这里的百分比必须包含小数点)
-XX:MaxRAMPercentage=25.0 # 如果未显式配置 -Xmx ,将将 Cgroup memory 限额的该百分比赋值给 -Xmx
-XX:MinRAMPercentage=50.0
# 关于 ParallelGC
-XX:+UseParallelGC
-XX:ParallelGCThreads=?? # 并行工作时,创建多少个 GC 线程。默认等于主机的 CPU 核数
# 关于 ConcMarkSweepGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection # 采用 ConcMarkSweepGC 时默认启用该功能,在 full GC 时自动压缩内存碎片
-XX:CMSInitiatingOccupancyFraction=-1 # 如果 old 区域内存使用率超过该百分比(比如 70~80),则触发 old GC 。默认为 -1 ,表示由 JVM 自动调整该阈值,一般足够智能
-XX:+UseCMSInitiatingOccupancyOnly # 是否保持 CMSInitiatingOccupancyFraction 阈值,不让 JVM 自动调整。默认禁用该功能
-XX:ConcGCThreads=?? # 并发标记时,创建多少个 GC 线程。默认等于 (cpu_cores+3)/4 ,至少为 1
# 关于 G1GC
# G1GC 的配置参数较少,因为它默认会自动调整大部分配置参数,一般不需要用户干预
-XX:+UseG1GC
-XX:G1HeapRegionSize=1M # 每个 region 的容量,取值范围为 1M~32M ,必须是 2 的幂级数
# 默认会根据 InitialHeapSize 自动决定 G1HeapRegionSize ,使得至少创建 2048 个 region 。region 容量越大,则清理耗时越长
-XX:MaxGCPauseMillis=200 # 每次 GC 预期的 STW 最大时长,默认为 200 毫秒。该值不应该配置得太小,否则会增加 GC 次数。而且这是一个软限制,不能肯定低于该时长
-XX:GCTimeRatio=9 # 控制 GC 耗时的比例,取值范围为 1~99 ,默认为 9 。假设 JVM 累计运行时长为 T ,则会限制 GC 累计耗时小于 T/(1+GCTimeRatio)
-XX:InitiatingHeapOccupancyPercent=45 # 堆内存的使用率超过 45% 时,触发一次 mixed GC
# 关于 OutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError # 抛出 OutOfMemoryError 错误时,自动生成堆快照文件,保存到磁盘
-XX:HeapDumpPath=java_pid$PID.hprof # 堆快照文件的保存路径,支持相对路径
-XX:ExitOnOutOfMemoryError # 抛出 OutOfMemoryError 错误时,终止 Java 进程
-XX:OnOutOfMemoryError="kill -9 %p" # 抛出 OutOfMemoryError 错误时,执行 shell 命令
# 关于 GC 日志
-XX:+PrintGCDetails # 打印 GC 详细日志。默认不打印 GC 日志
-XX:+PrintGCDateStamps # 每行 GC 日志的开头加上日期时间戳
-XX:+PrintHeapAtGC # 每次 GC 前后,打印一次当前的 Heap 信息
-Xloggc:gc.log # 将 GC 日志保存到文件中。默认打印到 stdout
-XX:+UseGCLogFileRotation # 自动轮换日志文件。默认禁用该功能
-XX:NumberOfGCLogFiles=0 # 保留多少个日志文件,默认不限制。命名格式为 gc.log.0、gc.log.1 等
-XX:GCLogFileSize=8K # 每个日志文件的最大体积
# 其它配置
-XX:+OmitStackTraceInFastThrow # 重复打印一个异常时,省略堆栈信息。默认启用该功能
启动 Java 进程时,一般的命令格式为
java [-options] -jar xx.jar
。- 如果将
-options
放在-jar xx.jar
之后,则不会生效。 - 如果重复声明某个配置参数,比如
-Xmx1G -Xmx2G
,则会采用最后配置的值。 - 可用
-XX:+FLAG
的格式启动一个功能标志,用-XX:-FLAG
的格式关闭功能。
- 如果将
java 启动命令的示例:
java -XX:MaxRAMPercentage=80.0 \ -XX:InitialRAMPercentage=80.0 \ -XX:+UseG1GC \ -XX:ConcGCThreads=8 \ -XX:ParallelGCThreads=8 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/heapdump.hprof \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:/tmp/gc-%t.log \ -XX:+UseGCLogFileRotation \ -XX:NumberOfGCLogFiles=5 \ -XX:GCLogFileSize=10M \ -Duser.timezone=GMT+08 \ -jar \ test.jar
建议启用 -XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 在抛出 OutOfMemoryError 错误时自动生成堆快照文件,方便排查问题。
- 堆快照(heapdump)是一种二进制文件,记录了所有 Java 对象的内存大小等信息,可用 MAT 等工具分析。
建议监控 Java 进程平时实际占用的内存大小,据此配置内存参数 -Xms 和 -Xmx 。
- JVM 实际占用的内存可能低于 -Xms ,因为 GC 时可能释放堆内存给操作系统。也可能高于 -Xmx ,因为 DirectMemory、Metaspace 等区域也会占用内存。
- 建议配置的 -Xms 等于 -Xmx ,让 JVM 启动时就一次向操作系统申请这么多内存,因为频繁申请内存的开销较大。
- 如果监控到 young GC 次数多,则在不优化 Java 程序代码的情况下,可增加 -Xmx ,或减少 -XX:NewRatio ,从而增加年轻代内存。如果监控到 old GC、full GC 次数多,对策同理。
- 在 k8s Pod 中部署 Java 进程时,配置的 limits.memory 取值应该比 Java -Xmx 大一些。因为 Java 进程占用的总内存包括 Heap、Direct Memory、Metaspace 等,可能超过 -Xmx ,如果再超过 limits.memory ,就会导致 Pod 被 OOM 杀死,然后自动重启。
- 可根据 Pod 的 limits.memory ,按比例配置 JVM 内存。例如,将传统的启动命令
java -Xms1G -Xmx1G -jar xx.jar
改为java -XX:InitialRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0 -jar xx.jar
。 - 可在 Pod 中执行
java -XX:MaxRAMPercentage=80.0 -XX:+PrintFlagsFinal 2>&1 | grep HeapSize
,测试此时的 HeapSize 取值是多少。
- 可根据 Pod 的 limits.memory ,按比例配置 JVM 内存。例如,将传统的启动命令
JVM GC 的目的是销毁垃圾对象,一般不会将 committed_memory 中的空闲内存释放给操作系统。
- 例如 JVM 向操作系统申请了 6G 堆内存,实际只使用 2G 内存,剩下 4G 的空闲内存占着不释放。
- 采用 Serial GC 时,可调整以下配置参数,强制让 JVM 释放空闲内存,但会增加 CPU 开销。
-XX:MinHeapFreeRatio=0 # GC 之后,空闲内存的预期最小百分比。如果低于该值,则申请更多空闲内存 -XX:MaxHeapFreeRatio=100 # GC 之后,空闲内存的预期最大百分比。如果超过该值,则释放一些空闲内存,还给操作系统。默认为 100% ,即不释放
# jps
jps # 列出当前主机上运行的所有 Java 进程的 PID 、名称
-l # 显示执行的 jar 包名,或主类名
# jmap
jmap [options] <pid> # 通过 PID 指定一个 Java 进程,显示其信息(比如使用哪种 GC 收集器)
-heap # 显示堆内存各个区域的大小
-dump:format=b,file=heapdump.hprof # 生成堆快照文件
# Ant
:一个 Java 项目的构建工具,可以自动进行 Java 项目的编译、测试、打包。
- 官方文档 (opens new window)
- Ant 最初是用于构建 Tomcat ,后来在 2000 年作为一个独立项目发布。
- 用法:
- 为 Java 项目创建一个 build.xml 文件,作为 Ant 的配置文件。
- 使用 ant 命令编译:
ant [target]... -f build.xml
# 配置
build.xml 的内容示例:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 项目名、项目目录、默认执行的任务 -->
<project name="test" basedir="/opt/test" default="compile">
<!-- 定义一些属性,属性名由用户自定义,可像变量一样使用 -->
<property name="src.dir" value="src"/>
<property name="lib.dir" value="lib"/>
<property name="classes.dir" value="classes"/> <!-- 编译出的类文件的保存路径 -->
<property name="build.dir" value="build"/>
<property name="jre.dir" value="/usr/local/jdk1.8.0_101/jre/lib"/>
<!-- 配置一些路径 -->
<path id="compile.dependencies">
<fileset dir="${lib.dir}" includes="*.jar"/>
</path>
<path id="java.dependencies">
<fileset dir="${jre.dir}" includes="*.jar"/>
</path>
<!-- 定义一个 target ,作为被 Ant 执行的任务 -->
<target name="init">
<delete dir="${classes.dir}"/>
<delete dir="${build.dir}"/>
<mkdir dir="${classes.dir}"/>
<mkdir dir="${build.dir}"/>
</target>
<target name="compile" depends="init">
<javac debug="true" includeantruntime="false" srcdir="${src.dir}" destdir="${classes.dir}" encoding="UTF-8">
<!-- <compilerarg line="-XDignore.symbol.file"/> -->
<classpath>
<path refid="compile.dependencies"/>
<path refid="java.dependencies"/>
</classpath>
</javac>
</target>
<target name="build_war" depends="compile">
<war destfile="${build.dir}/${ant.project.name}.war" webxml="${build.dir}/WEB-INF/web.xml">
<fileset dir="${build.dir}"/>
<lib dir="${build.dir}/WEB-INF/lib"/>
<classes dir="${build.dir}/WEB-INF/classes"/>
</war>
</target>
</project>
# Maven
:一个 Java 项目的构建、管理工具,既可以构建 Java 项目,还可以下载、制作、管理 jar 包等构建产物。
- 官方文档 (opens new window)
- 读音为
['meɪv(ə)n]
- 于 2004 年发布,比 Ant 的功能更多。
- 基于 POM(Project Object Model ,项目对象模型)管理 Java 项目。
- 用法:
- 调整 Java 项目的目录结构,以符合 POM 的规范。
- 使用 mvn 命令编译。
# 安装
- 用 yum 安装:
yum install maven
- 或者下载二进制版:
VERSION=3.8.4 wget https://dlcdn.apache.org/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz tar -zxvf apache-maven-$VERSION-bin.tar.gz -C /usr/local/ echo "export MAVEN_HOME=/usr/local/apache-maven-$VERSION" >> /etc/profile echo "export PATH=$PATH:$MAVEN_HOME/bin" >> /etc/profile source /etc/profile
- 或者用 Docker 部署:
docker run -it --rm \ -v maven-repo:/root/.m2 \ # 挂载本地仓库 -v $PWD:/data \ # 挂载项目目录 --workdir /data \ maven:3.8.4-jdk-8 \ mvn clean package
# 命令
mvn
compile # 编译当前项目
test # 在 compile 的基础上,测试项目。这会先编译出测试用例的类文件,然后执行它们
package # 在 test 的基础上,打包项目。一般是生成 jar 包或 war 包
install # 在 package 的基础上,将构建产物安装到本地仓库
deploy # 在 install 的基础上,将构建产物上传到远程仓库
clean # 清理项目目录,比如删除 target 目录下的文件
clean package # 先执行 clean ,再执行另一个操作(也可以换成 install 等操作)
-D <property>=<value> # 配置一个属性的值。可以多次使用该选项
maven.test.skip=true # 跳过 test 步骤
file.encoding=UTF-8 # 配置编码格式
-P dev,test # 激活一些配置文件(profiles),用逗号分隔
-pl module1,module2,... # --projects ,只构建指定的模块。默认构建当前项目的所有模块
-am # --also-make ,指定了 -pl 时,也构建它们依赖的模块
-v # 显示版本信息
-X # 显示调试信息
dependency:list # 列出项目的所有依赖
dependency:tree # 以树形结构列出项目的所有依赖,可以看出它们之间的依赖关系
- 执行
mvn deploy
时,会依次执行多个步骤:resources、compile、testResources、testCompile、test、jar、install、deploy 。- 执行
mvn package
时,只是执行到 jar 打包步骤就停止。 - 执行
mvn clean deploy
时,会先执行 clean 步骤。
- 执行
- 执行 mvn 命令时,会创建一个 java 子进程来进行 Maven 构建。
- 建议声明环境变量
export MAVEN_OPTS=-Xmx1g
来限制其占用的内存。默认会占用较多内存,却并不能明显提高编译速度。
- 建议声明环境变量
- 例:创建一个 webapp 类型的项目
mvn archetype:generate -DgroupId=com.example # 组织名(网址倒序) -DartifactId=web_demo # 项目名 -DarchetypeArtifactId=maven-archetype-webapp # 使用的项目模板 -DinteractiveMode=false # 是否进入交互模式
- 例:安装指定路径的 jar 包到本地仓库
mvn install:install-file -Dfile=targets/demo.jar \ -DgroupId=com.test -DartifactId=demo -Dversion=0.0.1 -Dpackaging=jar
- 例:下载指定的 jar 包到本地仓库
mvn org.apache.maven.plugins:maven-dependency-plugin:2.10:get \ -Dartifact=com.test:demo:0.0.1 # -DremoteRepositories=http://repo1.maven.org/maven2/ # 指定远程仓库,这会覆盖 pom.xml 和 settings.xml 的配置 # -Dtransitive=true # 是否下载依赖的其它包
# 目录结构
Maven 项目的目录结构示例:
web_demo ├── pom.xml # 该 Maven 项目的配置文件 ├── src │ ├── main # 存放源代码 │ │ ├── java # 存放代码文件 │ │ ├── resources # 存放资源文件 │ │ └── webapp │ └── test # 存放测试代码 │ ├── java │ └── resources └── target # 存放 Maven 的编译产物 ├── classes ├── maven-archiver │ └── pom.properties ├── web_demo # 编译后的 webapp 目录,可以打包成 war 包 │ ├── index.jsp │ ├── META-INF │ └── WEB-INF │ ├── classes │ └── web.xml └── web_demo.war # war 包
Maven 项目支持创建多个模块(module)。
- 每个模块相当于一个子 Maven 项目,位于一个子目录中。分别有一个 pom.xml ,继承父 pom.xml 。
- 目录结构示例:
web_demo ├── pom.xml ├── module1 │ ├── pom.xml │ └── src └── module2 ├── pom.xml └── src
# pom.xml
配置文件 pom.xml 示例:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 声明 pom.xml 的格式版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 该 Maven 项目的首要信息,用于在编译后打包成一个工件 -->
<name>test_project</name>
<description>The project is for test</description>
<groupId>in.freewind</groupId>
<artifactId>test_project</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<!-- 声明该项目的所有模块。会自动根据它们之间的依赖关系,确定编译顺序 -->
<modules>
<module>module1</module>
<module>module2</module>
</modules>
<!-- 声明构建项目时的配置。建议采用默认配置 -->
<build>
<sourceDirectory> ${basedir}/src/main/java </sourceDirectory>
<outputDirectory> ${basedir}/target/classes </outputDirectory>
<testSourceDirectory> ${basedir}/src/test/java </testSourceDirectory>
<testOutputDirectory> ${basedir}/target/test-classes </testOutputDirectory>
</build>
<!-- 声明一些属性,相当于全局变量,可以在 pom.xml 文件的其它位置用 ${propertie} 的方式调用 -->
<properties>
<!-- JDK 的版本 -->
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<!-- 声明用于下载工件的远程仓库。可以声明多个,依次尝试从其中下载工件。如果都不可用,则默认采用中央仓库 central -->
<repositories>
<repository>
<id>releases</id>
<url>http://nexus.test.com/repository/maven-releases/</url>
<!-- 仓库默认允许下载 releases 工件,不允许下载 snapshots 工件 -->
<!--
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
-->
</repository>
<repository>
<id>snapshots</id>
<url>http://nexus.test.com/repository/maven-snapshots/</url>
<snapshots>
<enabled>true</enabled>
<!-- 更新工件的频率,即下载相同名称、版本,但修改时间更加新的工件。默认为 daily -->
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<!-- 声明用于上传工件的远程仓库 -->
<distributionManagement>
<repository>
<id>releases</id>
<url>http://nexus.test.com/repository/maven-releases/</url>
</repository>
<!-- 如果声明了 snapshotRepository ,则采用它上传 snapshot 工件,否则采用 repository -->
<snapshotRepository>
<id>snapshots</id>
<url>http://nexus.test.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<!-- 声明该项目依赖的所有工件 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.1.5</version>
<!-- <type>jar</type> -->
</dependency>
<dependency>
<groupId>com.test</groupId>
<artifactId>test</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependency>
<!-- 可以定义一些可选配置(profile),用于覆盖当前的 POM 配置。可以通过 mvn -P 选项主动激活,也可以通过 activation 条件自动激活 -->
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
<activation>
<activeByDefault>false</activeByDefault>
<jdk>1.8</jdk>
</activation>
</profile>
<profile>
<id>test</id>
<repositories>...</repositories>
</profile>
</profiles>
</project>
- 官方文档 (opens new window)
- Artifact 的命名格式为
groupId:artifactId:version[:packaging[:classifier]]
。- groupId :制作者的团体名称,一般使用倒序的域名。
- artifactId :工件的名称,在同一个 groupId 下唯一。
- version :工件的版本。分为两种格式:
- releases :正式版本,比如
1.0.0
。构建项目时,先尝试从本地仓库获取相同名称、版本的依赖工件,如果不存在,再从远程仓库下载到本地仓库,缓存起来。 - snapshots :快照版本,比如
1.0.0-SNAPSHOT
。构建项目时,总是尝试从远程仓库获取相同名称、版本,但修改时间更加新的依赖工件,覆盖本地仓库的缓存。
- releases :正式版本,比如
- packaging :打包格式,默认为 jar 。
- classifier :分类器,可以是一个任意字符串。
- Maven 仓库用于存储 Artifact 文件,分为两种:
- 本地仓库
- :位于本机某个目录下(默认为
~/.m2/repository/
),用于缓存从远程仓库下载的依赖。
- :位于本机某个目录下(默认为
- 远程仓库
- :位于其它主机上的仓库,可用于下载、上传工件。比如用 Nexus 搭建的私有仓库。
- Maven 社区维护了一个远程仓库,称为中央仓库(central)。可在
https://search.maven.org/
网页搜索,或浏览其文件列表https://repo1.maven.org/maven2/
。 - 在 pom.xml 中可以配置当前项目使用的远程仓库,在 settings.xml 中可以配置全局的远程仓库。
- 本地仓库
# settings.xml
执行 mvn 命令时,还会读取以下位置的配置文件:
~/.m2/settings.xml # 当前用户的配置文件
$MAVEN_HOME/conf/settings.xml # 全局的配置文件
settings.xml 示例:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 配置远程仓库的访问账号 -->
<servers>
<server>
<id>releases</id>
<username>***</username>
<password>***</password>
</server>
<server>
<id>mirror1</id>
<username>***</username>
<password>***</password>
</server>
</servers>
<!-- 声明镜像仓库 -->
<mirrors>
<mirror>
<id>mirror1</id>
<!-- 用该镜像仓库替换哪些 id 的远程仓库 -->
<mirrorOf>*</mirrorOf>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
</mirrors>
</settings>
- 镜像仓库(mirror):一些更高优先级的远程仓库。可以拦截发向普通远程仓库的请求,交给镜像仓库处理。
- 只会拦截下载工件的请求,不会拦截上传工件的请求。
- 如果一个远程仓库匹配多个镜像仓库,则只采用第一个镜像仓库。
- 如果镜像仓库不可访问,并不会再尝试访问原来的远程仓库。
- mirrorOf 的取值示例:
* # 匹配所有远程仓库 central # 匹配中央仓库 repo1,repo2 # 匹配指定 id 的远程仓库,不支持匹配 mirror id !repo1,* # 排除 repo1 ,然后匹配其它远程仓库
# Gradle
:一个 Java 项目的构建工具,于 2012 年发布。
- 官方文档 (opens new window)
- 基于 Groovy 语言编写构建脚本,因此比 Maven 更灵活。