转载

如何构建一个 Java 工程

首先,编译器需要将 .java 文本文件编译为 .class 字节码,然后 JVM 执行 .class 字节码文件。流程并不复杂,本文主要记录一些在编译、运行时的相关过程。

1. 单个文件源代码

  1. 新建文本文件 Hello.java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
  1. 编译源码
javac Hello.java
  1. 执行字节码
java Hello

2. 多个源码文件

  • 使用命令行指定多个文件
javac M.java E.java
  • 使用文本指定多个文件
# 查找当前目录下的 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

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();
    }
}

3.2 编译 Java 源码

javac com/test/*.java

3.3 打包 Jar 文件

使用 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

3.4 执行 Jar 文件

直接执行 Jar 文件,会报错:

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) 所在类。

  • 通过命令行指定包含 main 函数的类
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 上安装为例:

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"

4.2 创建一个 pom.xml 文件

以上面的 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 信息。

4.3 将项目打包到镜像

为了能将项目直接部署在容器平台,编译构建之后,我们还需要将生成的文件容器化。这里使用 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

5. 参考

  • https://www.ibm.com/developerworks/cn/java/j-jar/index.html
  • https://zh.wikipedia.org/wiki/Apache_Maven
原文  https://www.chenshaowen.com/blog/how-to-compile-a-java-project.html
正文到此结束
Loading...