首先,编译器需要将 .java 文本文件编译为 .class 字节码,然后 JVM 执行 .class 字节码文件。流程并不复杂,本文主要记录一些在编译、运行时的相关过程。
public class Hello { public static void main(String[] args) { System.out.println("Hello, world!"); } }
javac Hello.java
java Hello
javac M.java E.java
# 查找当前目录下的 Java 源码文件 find -name "*.java" > source.txt # 编译 javac @source.txt
执行时,只需执行包含 main 函数的类即可,例如, java M
。
上面的编译命令会在当前目录下生成 .class
字节码文件,也可以通过 -d
参数指定生成目录。管理这些字节码文件会是一件繁琐、麻烦的事情,而 Jar 包简化了这个过程。
Jar 文件以 Zip 格式为基础,将多个文件聚合为一个文件。Jar 文件不仅可以用于压缩、发布,还可以用于部署、封装库、组件、插件程序,同时还可以在 JVM 上直接运行。Jar 包提供如下特性:
使用 IDE 工具,可以很方便地创建一个 Jar 文件,例如,Myeclipse,可以自行尝试。这里直接使用 jar
命令,生成 Jar 文件。
这里以多源码文件为例,在 com/test 目录下创建两个文件:
A.java
package com.test; public class A { public static void test() { System.out.println("A:test()"); } }
B.java
package com.test; import com.test.A; public class B { public static void main(String[] argc) { A a = new A(); a.test(); } }
javac com/test/*.java
使用 jar
命令打包 Jar 包,与使用 tar
命令类似。
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
直接执行 Jar 文件,会报错:
java -jar test.jar no main manifest attribute, in test.jar
这是由于 JVM 找不到程序的执行入口。有两种方法可以指定程序入口:
使用 unzip
命令解压 Jar 文件,可以看到除了 .class 文件,还有一个 META-INF/MANIFEST.MF
文件。在 META-INF/MANIFEST.MF
文件中,新增:
Main-Class: com.test.B
指向 public static void main(String[] args)
所在类。
java -cp test.jar com.test.B A:test()
如果只有一两个源码文件,上面的打包过程尚可接受。而对于中大型项目,这种原始的方式无法满足构建和管理的需求,需要借助一定的工具。
Maven 是一个软件项目管理及自动构建工具,可以用于构建和管理各种项目,例如,Java、Ruby、Scala 等。Maven 是 Apache 软件基金会下的项目。
Maven项目使用项目对象模型(Project Object Model,POM)来配置。项目对象模型存储在名为 pom.xml 的文件中。
这里以在 CentOS 上安装为例:
yum install -y maven
查看版本:
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"
以上面的 A.class,B.class 为例,新建一个 pom.xml 文件。artifactId 为构建之后生成的文件名。
在 Maven 项目中,约定主代码放到 src/main/java
目录下,而无需额外配置。这里新建 src/main/java
目录,将 com 目录移入其中。
新建 pom.xml 文件如下:
<?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>
最终的目录结构为:
tree . |-- pom.xml |-- src | `-- main | `-- java | `-- com | `-- test | |-- A.java | `-- B.java
执行命令,编译项目:
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] ------------------------------------------------------------------------
执行构建包:
java -jar target/test-0.0.1-SNAPSHOT.jar A:test()
这里可以直接 java -jar
执行的原因是,在 pom.xml 中添加了插件 maven-jar-plugin
,该插件会在 META-INF/MANIFEST.MF
中添加 Main-Class 信息。
为了能将项目直接部署在容器平台,编译构建之后,我们还需要将生成的文件容器化。这里使用 docker-maven-plugin
插件来实现。在 pom.xml 文件 plugins
标签中新增如下内容:
<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 仓库。
查看本地构建的镜像:
docker images|grep hello shaowenchen/maven-hello-word v1 ade23e55f848 About a minute ago 488MB