Maven 学习笔记
发布于 2025-01-12
通过命令行创建空的 maven
项目骨架
1. 从 Maven 模板创建项目1
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=my-maven-demo \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
... [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0 [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: basedir, Value: /tmp [INFO] Parameter: package, Value: com.example [INFO] Parameter: groupId, Value: com.example [INFO] Parameter: artifactId, Value: my-maven-demo [INFO] Parameter: packageName, Value: com.example [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] project created from Old (1.x) Archetype in dir: /tmp/my-maven-demo [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.908 s [INFO] Finished at: 2025-01-17T23:21:31+08:00 [INFO] ------------------------------------------------------------------------
2. 生成的项目目录结构
$ tree my-maven-demo maven-demo ├── pom.xml └── src ├── main │ └── java │ └── com │ └── example │ └── App.java └── test └── java └── com └── example └── AppTest.java 10 directories, 3 files
3. 生成 pom.xml
文件内容
生成的内容如下, 仅包含一个 jUnit 依赖项.
<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.example</groupId> <artifactId>my-maven-demo</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>maven-demo</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
4. 更新 pom.xml
文件
4.1 增加编译器属性,告诉 Maven 使用指定的 JDK 版本来编译源码
<properties> <!-- 指定项目构建时的源文件编码格式, 防止源文件中的非 ASCII 字符出现乱码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 指定项目源代码所使用的 Java 版本 --> <maven.compiler.source>1.8</maven.compiler.source> <!-- 指定编译后的字节码目标版本 --> <maven.compiler.target>1.8</maven.compiler.target> </properties>
4.2 升级到 jUnit4
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
Maven 项目构建与运行
接上面创建的 maven 项目骨架,继续进行项目的编译与打包.
1. 增加依赖项
为更好的演示打包的效果, 增加一个依赖项.
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.17.2</version> </dependency>
2. 编写代码
2.1 编写代码
src/main/java/com/example/App.java
package com.example; import org.apache.commons.codec.digest.DigestUtils; public class App { public static void main(String[] args) { if (args.length < 1) { System.err.println("请输入任意字符串!"); System.exit(0); } System.out.println("SHA-256哈希值为: " + sha256hex(args[0])); } public static String sha256hex(String input) { return DigestUtils.sha256Hex(input); } }
2.2 编写单元测试
com/example/AppTest.java
package com.example; import org.junit.Test; import static org.junit.Assert.*; public class AppTest { @Test public void sha256hex() { assertEquals("8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", App.sha256hex("123456")); } }
3. maven 构建
执行 mvn package
完成构建
$ mvn package [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:my-maven-demo >---------------------- [INFO] Building my-maven-demo 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ my-maven-demo --- [INFO] skip non existing resourceDirectory /tmp/my-maven-demo/src/main/resources [INFO] [INFO] --- compiler:3.13.0:compile (default-compile) @ my-maven-demo --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 1 source file with javac [debug target 1.8] to target/classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ my-maven-demo --- [INFO] skip non existing resourceDirectory /tmp/my-maven-demo/src/test/resources [INFO] [INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ my-maven-demo --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 1 source file with javac [debug target 1.8] to target/test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ my-maven-demo --- [INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.example.AppTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.111 s -- in com.example.AppTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- jar:3.4.1:jar (default-jar) @ my-maven-demo --- [INFO] Building jar: /tmp/my-maven-demo/target/my-maven-demo-1.0-SNAPSHOT.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.145 s [INFO] Finished at: 2025-01-16T23:40:30+08:00 [INFO] ------------------------------------------------------------------------
4. 运行构建后得到的 jar 包
4.1 尝试执行
java -cp target/my-maven-demo-1.0-SNAPSHOT.jar com.example.App 123456
发现报错了, 报错原因是根据当前的 pom 文件配置, Maven 不会将项目中的依赖项 commons-codec
添加到 jar 文件中。
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/codec/digest/DigestUtils at com.example.App.sha256hex(App.java:16) at com.example.App.main(App.java:12) Caused by: java.lang.ClassNotFoundException: org.apache.commons.codec.digest.DigestUtils at java.net.URLClassLoader.findClass(URLClassLoader.java:387) at java.lang.ClassLoader.loadClass(ClassLoader.java:418) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ... 2 more
4.2 使用 maven-shade-plugin
2 插件来生成 fat-jar
- 将所有依赖项打包进同一个 jar 文件中
更新 pom.xml
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.6.0</version> <executions> <!-- 指定该插件的 shade 目标在 Maven 的 package 阶段执行 --> <!-- 也就是当运行 mvn package 命令时,该插件的 shade 目标将被自动触发 --> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
4.3 重新构建 mvn clean package
... [INFO] --- jar:3.4.1:jar (default-jar) @ my-maven-demo --- [INFO] Building jar: /tmp/my-maven-demo/target/my-maven-demo-1.0-SNAPSHOT.jar [INFO] [INFO] --- shade:3.6.0:shade (default) @ my-maven-demo --- [INFO] Including commons-codec:commons-codec:jar:1.17.2 in the shaded jar. [INFO] Dependency-reduced POM written at: /tmp/my-maven-demo/dependency-reduced-pom.xml [WARNING] commons-codec-1.17.2.jar, my-maven-demo-1.0-SNAPSHOT.jar define 1 overlapping resource: [WARNING] - META-INF/MANIFEST.MF [WARNING] maven-shade-plugin has detected that some files are [WARNING] present in two or more JARs. When this happens, only one [WARNING] single version of the file is copied to the uber jar. [WARNING] Usually this is not harmful and you can skip these warnings, [WARNING] otherwise try to manually exclude artifacts based on [WARNING] mvn dependency:tree -Ddetail=true and the above output. [WARNING] See https://maven.apache.org/plugins/maven-shade-plugin/ [INFO] Replacing original artifact with shaded artifact. [INFO] Replacing /tmp/my-maven-demo/target/my-maven-demo-1.0-SNAPSHOT.jar with /tmp/my-maven-demo/target/my-maven-demo-1.0-SNAPSHOT-shaded.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.268 s [INFO] Finished at: 2025-01-16T23:58:36+08:00 [INFO] ------------------------------------------------------------------------
可以看到生成了两个 jar 包, 查看一下它们的文件大小
$ ls -lh target ... -rw-r--r-- 1 demo demo 365K Jan 16 23:58 my-maven-demo-1.0-SNAPSHOT.jar -rw-r--r-- 1 demo demo 2.9K Jan 16 23:58 original-my-maven-demo-1.0-SNAPSHOT.jar ...
5. 第 2 次运行
5.1 尝试运行
$ java -cp target/my-maven-demo-1.0-SNAPSHOT.jar com.example.App 123456 SHA-256哈希值为: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
5.2 很好, 现在执行成功了。但是能不能直接作为 jar 包执行而不用指定主类呢
➜ java -jar target/my-maven-demo-1.0-SNAPSHOT.jar no main manifest attribute, in target/my-maven-demo-1.0-SNAPSHOT.jar
5.3 很遗憾,还不可以,可以通过配置 maven-shade-plugin 插件来指定主类
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.6.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.App</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
6. 第 3 次运行
6.1 重新打包
mvn clean package
6.2 作为 jar 包执行
➜ java -jar target/my-maven-demo-1.0-SNAPSHOT.jar 请输入任意字符串! ➜ java -jar target/my-maven-demo-1.0-SNAPSHOT.jar 123456 SHA-256哈希值为: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
7. 完整的 pom 文件
<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.example</groupId> <artifactId>my-maven-demo</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>my-maven-demo</name> <url>http://maven.apache.org</url> <properties> <!-- 指定项目构建时的源文件编码格式, 防止源文件中的非 ASCII 字符出现乱码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 指定项目源代码所使用的 Java 版本 --> <maven.compiler.source>1.8</maven.compiler.source> <!-- 指定编译后的字节码目标版本 --> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.17.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.6.0</version> <executions> <!-- 指定该插件的 shade 目标在 Maven 的 package 阶段执行 --> <!-- 也就是当运行 mvn package 命令时,该插件的 shade 目标将被自动触发 --> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.App</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
8. 体验更高版本的 maven-archetype-quickstart
8.1 在步骤 1 的输出中可以看到默认使用的 maven-archetype-quickstart 版本是 1.0. 通过官网查看目前的最新版本已经是 1.5 了, 体验一下新版本3. 可以直接指定 java 编译器版本, jUnit 版本等。
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=maven-demo \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.5 \ -DinteractiveMode=false \ -DjavaCompilerVersion=1.8 \ -DjunitVersion=4.13.2
... [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Archetype: maven-archetype-quickstart:1.5 [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: com.example [INFO] Parameter: artifactId, Value: maven-demo [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: package, Value: com.example [INFO] Parameter: packageInPathFormat, Value: com/example [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: package, Value: com.example [INFO] Parameter: javaCompilerVersion, Value: 1.8 [INFO] Parameter: groupId, Value: com.example [INFO] Parameter: junitVersion, Value: 4.13.2 [INFO] Parameter: artifactId, Value: maven-demo [WARNING] Don't override file /tmp/maven-demo/src/main/java/com/example [WARNING] Don't override file /tmp/maven-demo/src/test/java/com/example [WARNING] CP Don't override file /tmp/maven-demo/.mvn [INFO] Project created from Archetype in dir: /tmp/maven-demo [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.926 s [INFO] Finished at: 2025-01-17T23:53:49+08:00 [INFO] ------------------------------------------------------------------------
8.2 查看项目目录
➜ tree -a maven-demo maven-demo ├── .mvn │ ├── jvm.config │ └── maven.config ├── pom.xml └── src ├── main │ └── java │ └── com │ └── example │ └── App.java └── test └── java └── com └── example └── AppTest.java 11 directories, 5 files
8.3 pom 文件内容
<?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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>maven-demo</artifactId> <version>1.0-SNAPSHOT</version> <name>maven-demo</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.4.0</version> </plugin> <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.3.0</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.4.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>3.1.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>3.1.2</version> </plugin> <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.12.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.6.1</version> </plugin> </plugins> </pluginManagement> </build> </project>
Footnotes:
Homepage of maven-shade-plugin https://maven.apache.org/plugins/maven-shade-plugin/index.html
Homepage of maven-archetype-quickstart https://maven.apache.org/archetypes/maven-archetype-quickstart/