首先,编译器需要将 .java 文本文件编译为 .class 字节码,然后 JVM 执行 .class 字节码文件。流程并不复杂,本文主要记录一些在编译、运行时的相关过程。
1. 单个文件源代码
新建文本文件 Hello.java
1
2
3
4
5
|
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
|
编译源码
执行字节码
2. 多个源码文件
1
2
3
4
|
# 查找当前目录下的 Java 源码文件
find -name "*.java" > source.txt
# 编译
javac @source.txt
|
执行时,只需执行包含 main 函数的类即可,例如,java M
。上面的编译命令会在当前目录下生成 .class
字节码文件,也可以通过 -d
参数指定生成目录。管理这些字节码文件会是一件繁琐、麻烦的事情,而 Jar 包简化了这个过程。
3. Jar 文件
Jar 文件以 Zip 格式为基础,将多个文件聚合为一个文件。Jar 文件不仅可以用于压缩、发布,还可以用于部署、封装库、组件、插件程序,同时还可以在 JVM 上直接运行。Jar 包提供如下特性:
- 安全性。文件内容具有数字化签名
- 压缩文件、减少网络传输时间
- 平台扩展。使用 Jar 文件向 Java 平台核心扩展功能
- 包密封。存储在 Jar 文件中的包可以进行密封,以增强版本一致性和安全性
- 包版本控制。Jar 文件可以包含版本、开发者相关信息
- 可移植性。Java 平台核心对 Jar 文件的处理进行了规范
使用 IDE 工具,可以很方便地创建一个 Jar 文件,例如,Myeclipse,可以自行尝试。这里直接使用 jar
命令,生成 Jar 文件。
3.1 准备 Java 源码
这里以多源码文件为例,在 com/test 目录下创建两个文件:A.java
1
2
3
4
5
6
7
|
package com.test;
public class A {
public static void test() {
System.out.println("A:test()");
}
}
|
B.java
1
2
3
4
5
6
7
8
9
|
package com.test;
import com.test.A;
public class B {
public static void main(String[] argc) {
A a = new A();
a.test();
}
}
|
3.2 编译 Java 源码
3.3 打包 Jar 文件
使用 jar
命令打包 Jar 包,与使用 tar
命令类似。
1
2
3
4
5
|
jar cvf test.jar com/test/*.class
added manifest
adding: com/test/A.class(in = 388) (out= 275)(deflated 29%)
adding: com/test/B.class(in = 315) (out= 236)(deflated 25%)
|
参考文档,Compiling the Example Programs
3.4 执行 Jar 文件
直接执行 Jar 文件,会报错:
1
2
3
|
java -jar test.jar
no main manifest attribute, in test.jar
|
这是由于 JVM 找不到程序的执行入口。有两种方法可以指定程序入口:
- 在 META-INF/MANIFEST.MF 文件中指定
使用 unzip
命令解压 Jar 文件,可以看到除了 .class 文件,还有一个 META-INF/MANIFEST.MF
文件。在 META-INF/MANIFEST.MF
文件中,新增:
Main-Class: com.test.B
指向 public static void main(String[] args)
所在类。
1
2
3
|
java -cp test.jar com.test.B
A:test()
|
4. Maven
如果只有一两个源码文件,上面的打包过程尚可接受。而对于中大型项目,这种原始的方式无法满足构建和管理的需求,需要借助一定的工具。Maven 是一个软件项目管理及自动构建工具,可以用于构建和管理各种项目,例如,Java、Ruby、Scala 等。Maven 是 Apache 软件基金会下的项目。Maven项目使用项目对象模型(Project Object Model,POM)来配置。项目对象模型存储在名为 pom.xml 的文件中。
4.1 安装 Maven
这里以在 CentOS 上安装为例:
查看版本:
1
2
3
4
5
6
7
8
|
mvn -v
Apache Maven 3.0.5 (Red Hat 3.0.5-17)
Maven home: /usr/share/maven
Java version: 1.8.0_232, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.232.b09-0.el7_7.x86_64/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "3.10.0-862.el7.x86_64", arch: "amd64", family: "unix"
|
4.2 创建一个 pom.xml 文件
以上面的 A.class,B.class 为例,新建一个 pom.xml 文件。artifactId 为构建之后生成的文件名。在 Maven 项目中,约定主代码放到 src/main/java
目录下,而无需额外配置。这里新建 src/main/java
目录,将 com 目录移入其中。新建 pom.xml 文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name> a maven project</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<!-- give full qualified name of your main class-->
<mainClass>com.test.B</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
|
最终的目录结构为:
1
2
3
4
5
6
7
8
9
10
11
|
tree
.
|-- pom.xml
|-- src
| `-- main
| `-- java
| `-- com
| `-- test
| |-- A.java
| `-- B.java
|
执行命令,编译项目:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
mvn clean package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building a maven project 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.1.0/maven-jar-plugin-3.1.0.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.1.0/maven-jar-plugin-3.1.0.pom (7 KB at 5.1 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.1.0/maven-jar-plugin-3.1.0.jar
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.1.0/maven-jar-plugin-3.1.0.jar (27 KB at 49.8 KB/sec)
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ test ---
[INFO] Deleting /root/test-java/target
[INFO]
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ test ---
[debug] execute contextualize
[WARNING] Using platform encoding (ANSI_X3.4-1968 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /root/test-java/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ test ---
[WARNING] File encoding has not been set, using platform encoding ANSI_X3.4-1968, i.e. build is platform dependent!
[INFO] Compiling 2 source files to /root/test-java/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ test ---
[debug] execute contextualize
[WARNING] Using platform encoding (ANSI_X3.4-1968 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /root/test-java/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ test ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ test ---
[INFO] No tests to run.
[INFO] Surefire report directory: /root/test-java/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Results :
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:3.1.0:jar (default-jar) @ test ---
Downloading: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-archiver/3.5/plexus-archiver-3.5.jar
Downloading: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-io/3.0.0/plexus-io-3.0.0.jar
Downloading: https://repo.maven.apache.org/maven2/org/tukaani/xz/1.6/xz-1.6.jar
Downloaded: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-archiver/3.5/plexus-archiver-3.5.jar (183 KB at 259.7 KB/sec)
Downloaded: https://repo.maven.apache.org/maven2/org/tukaani/xz/1.6/xz-1.6.jar (101 KB at 84.8 KB/sec)
Downloaded: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-io/3.0.0/plexus-io-3.0.0.jar (73 KB at 59.8 KB/sec)
[INFO] Building jar: /root/test-java/target/test-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.352s
[INFO] Finished at: Fri Dec 20 16:12:06 CST 2019
[INFO] Final Memory: 16M/249M
[INFO] ------------------------------------------------------------------------
|
执行构建包:
1
2
3
|
java -jar target/test-0.0.1-SNAPSHOT.jar
A:test()
|
这里可以直接 java -jar
执行的原因是,在 pom.xml 中添加了插件 maven-jar-plugin
,该插件会在 META-INF/MANIFEST.MF
中添加 Main-Class 信息。
4.3 将项目打包到镜像
为了能将项目直接部署在容器平台,编译构建之后,我们还需要将生成的文件容器化。这里使用 docker-maven-plugin
插件来实现。在 pom.xml 文件 plugins
标签中新增如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<imageName>
shaowenchen/maven-hello-word:v1
</imageName>
<registryUrl></registryUrl>
<workdir>/work</workdir>
<rm>true</rm>
<env>
<TZ>Asia/Shanghai</TZ>
<JAVA_OPTS>
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=2 \
-XX:CICompilerCount=8 \
-XX:ActiveProcessorCount=8 \
-XX:+UseG1GC \
-XX:+AggressiveOpts \
-XX:+UseFastAccessorMethods \
-XX:+UseStringDeduplication \
-XX:+UseCompressedOops \
-XX:+OptimizeStringConcat
</JAVA_OPTS>
</env>
<baseImage>openjdk:8</baseImage>
<cmd>
java ${JAVA_OPTS} -jar ${project.build.finalName}.jar
</cmd>
<!--是否推送image-->
<pushImage>false</pushImage>
<resources>
<resource>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<serverId>docker-hub</serverId>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
|
再次执行编译命令 mvn package
,会看到增加了一些日志。
[INFO] --- docker-maven-plugin:1.2.1:build (default) @ test ---
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[WARNING] No entry found in settings.xml for serverId=docker-hub, cannot configure authentication for that registry
[INFO] Using authentication suppliers: [ConfigFileRegistryAuthSupplier]
[INFO] Copying /root/test-java/target/test-0.0.1-SNAPSHOT.jar -> /root/test-java/target/docker/test-0.0.1-SNAPSHOT.jar
[INFO] Building image shaowenchen/maven-hello-word:v1
Step 1/6 : FROM openjdk:8
---> 09df0563bdfc
Step 2/6 : ENV JAVA_OPTS -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XX:CICompilerCount=8 -XX:ActiveProcessorCount=8 -XX:+UseG1GC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -XX:+UseStringDeduplication -XX:+UseCompressedOops -XX:+OptimizeStringConcat
---> Running in b6dabde9580c
Removing intermediate container b6dabde9580c
---> 0664556506d3
Step 3/6 : ENV TZ Asia/Shanghai
---> Running in 954b264bfb35
Removing intermediate container 954b264bfb35
---> 334f644fa97e
Step 4/6 : WORKDIR /work
---> Running in 44e039f55452
Removing intermediate container 44e039f55452
---> 3572d77be94c
Step 5/6 : ADD test-0.0.1-SNAPSHOT.jar .
---> 904b5885f74a
Step 6/6 : CMD java ${JAVA_OPTS} -jar test-0.0.1-SNAPSHOT.jar
---> Running in b3f567a912a5
Removing intermediate container b3f567a912a5
---> cba070b2300d
ProgressMessage{id=null, status=null, stream=null, error=null, progress=null, progressDetail=null}
Successfully built cba070b2300d
Successfully tagged shaowenchen/maven-hello-word:v1
[INFO] Built shaowenchen/maven-hello-word:v1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.017s
[INFO] Finished at: Fri Dec 20 16:27:48 CST 2019
[INFO] Final Memory: 31M/508M
[INFO] ------------------------------------------------------------------------
上面的日志是在构建镜像,如果打开推送开关,Maven 会将镜像推送到 dockerhub 仓库。查看本地构建的镜像:
1
2
3
|
docker images|grep hello
shaowenchen/maven-hello-word v1 ade23e55f848 About a minute ago 488MB
|
5. 参考
- https://www.ibm.com/developerworks/cn/java/j-jar/index.html
- https://zh.wikipedia.org/wiki/Apache_Maven