# 构建
- 简单的 Java 项目可以用 javac 命令直接编译,复杂的 Java 项目则通常用 Ant、Maven、Gradle 等构建工具自动地编译、打包。
- 构建 Java 项目时,通常将编译生成的类文件,和配置文件、依赖 jar 等文件一起,按 ZIP 格式做成一个压缩包,扩展名为 .jar 。
- 构建 Java Web 项目时,通常打包的扩展名为 .war 。
- jar 或 war 包是一个独立的 Java 程序,可以被 java 命令直接运行。
- Artifact :泛指项目的构建产物,又称为工件、构建物、制品。
# 编译
# 原理
Java 代码需要先编译才能运行,其流程如下:
- 将 .java 源文件编译成字节码,存储在 .class 类文件中。
- 由 JVM 读取字节码,解释成当前操作系统的机器代码并执行。
类文件 :扩展名为 .class ,用于存储 .java 源文件编译生成的字节码,因此又称为字节码文件。
字节码(Byte-code):一种二进制代码,是介于编程语言代码与机器代码之间的中间代码。
JDK :Java 开发环境工具包(Java SE Development Kit),包含 JRE 以及开发、调试、监控工具。
JRE :Java 运行环境工具包 (Java Runtime Environment),只提供了运行 Java 程序所需的 JVM 、类库,因此体积比 JDK 小。
JVM :Java 虚拟机(Java Virtual Machine),用于运行 Java 的类文件。
- Java 语言在不同操作系统上分别实现了 JVM ,使得同一个类文件可以被不同平台的 JVM 运行,而不必考虑平台的底层差异,不必修改代码或重新编译。
- C 代码编译是直接生成当前操作系统的机器代码,而 Java 代码编译是生成字节码,以便移植到不同机器平台的 JVM 上运行。
# 流程
基本的编译流程如下:
- 安装 JDK
- 编写一个源文件 Hello.java :
public class Hello { public static void main(String[] args) { System.out.println("Hello world"); } }
- 用 javac 命令编译源文件,生成类文件:
[root@CentOS ~]# ls Hello.java [root@CentOS ~]# javac Hello.java # 这会编译生成一个类文件 Hello.class [root@CentOS ~]# ls Hello.class Hello.java
- 用 java 命令运行类文件(实际上是交给 JVM 运行):
[root@CentOS ~]# java Hello Hello world [root@CentOS ~]# java Hello.class # 运行类文件时不需要输入扩展名 .class ,否则会被当做类名的一部分 Error: Could not find or load main class Hello.class
# JDK
# 安装
用包管理工具安装 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
# 命令
javac
*.java # 编译源文件
-sourcepath <path> # 查找源文件的路径
-classpath <path> # 用于查找类库的路径。该设置会覆盖环境变量 CLASSPATH
-d . # 编译出的类文件的保存路径
-encoding utf-8 # 源文件的编码格式
- path 默认为当前目录。如果指定多个路径,在 Winodws 上用 ; 分隔,在 Linux 上用 : 分隔。
java
<class name> # 运行类文件(在前台运行)
-jar xx.jar # 运行 jar 包
-jar xx.war --httpPort=8080 # 运行 war 包
-classpath <path> # 用于查找类库(通常保存为 jar 包)的路径。该设置会覆盖环境变量 CLASSPATH
-cp <path> # 等价于 -classpath 选项
-D <property>=<value> # 设置一个属性的值。可以多次使用该选项
user.timezone=GMT+08 # 设置时区
-Xms1g # -XX:InitialHeapSize ,堆内存的初始容量,默认为主机 RAM 的 1/64 。建议设置成与 -Xmx 相等,否则运行时可能频繁地调整大小,开销较大
-Xmx1g # -XX:MaxHeapSize ,堆内存的最大容量,默认为主机 RAM 的 1/4 。建议设置得足够大,避免因为内存不足而频繁 GC
-Xmn300m # 堆内存中 young 区域的大小,推荐为整个堆的 1/3 。剩下的堆内存则属于 old 区域
-Xss1m # 每个线程的堆栈大小,默认为 1M
-XX:MaxDirectMemorySize=1g # Direct Memory 的最大容量,达到限制则触发一次 Full GC 。默认等于 -Xmx
-XX:MetaspaceSize=20m # Metaspace 的初始容量,默认为 20.8M 。超过该容量之后,每次扩容都会触发 Full GC ,因此建议设置成与 MaxMetaspaceSize 相等
-XX:MaxMetaspaceSize=100m # Metaspace 的最大容量,默认不限制
-XX:+HeapDumpOnOutOfMemoryError # 当堆内存耗尽时,生成堆的内存快照
-XX:HeapDumpPath=./java_pid$PID.hprof # 快照文件的保存路径。它是二进制文件,需要用专用工具分析
-version # 显示 JDK 或 JRE 的版本信息
-XshowSettings:properties -version # 显示详细的版本信息
-verbose jar # 显示 jar 包的安装目录
- Java 进程即 JVM 占用的内存主要分为两部分:
- Heap Memory :堆内存,用于存放 Java 程序内部的数据,比如 Java 对象。
- Direct Memory :直接内存,用于存放与操作系统交互的数据,比如文件。
- Java 内存容量不能设置得太小。如果内存紧张,频繁 GC ,则会导致 CPU 负载大幅增加,甚至进程卡死、端口无响应。
jps # 列出当前主机上运行的所有 JVM 进程,及其 PID
-l # 显示执行的 jar 包名,或主类名
# 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 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 项目的构建工具。
- 官方文档 (opens new window)
- 于 2012 年发布。
- 基于 Groovy 语言编写构建脚本,因此比 Maven 更灵活。