如何构建一个 Java 工程

2023年 1月 4日 50.9k 0

首先,编译器需要将 .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!");
        }
    }
    
  • 编译源码
  • 1
    
    javac Hello.java
    
  • 执行字节码
  • 1
    
    java Hello
    

    2. 多个源码文件

    • 使用命令行指定多个文件
    1
    
    javac M.java E.java
    
    • 使用文本指定多个文件
    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 源码

    1
    
    javac com/test/*.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) 所在类。

    • 通过命令行指定包含 main 函数的类
    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
    
    yum install -y maven
    

    查看版本:

    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

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论