Original link: https://jasonkayzk.github.io/2023/03/20/%E4%BD%BF%E7%94%A8Java%E7%BC%96%E5%86%99Cli%E5%91%BD% E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7/
Going around, I recently returned to Java;
Recently, I am writing the Java version of mini-redis to learn Netty, and I need to use the Java command line tool framework picocli;
It is quite troublesome to realize the command line through Java, especially the packaging part, here is a brief summary;
source code:
Write Cli command line tool in Java
foreword
After trying it, it is still not recommended to use Java to develop Cli. After all, no one will install JRE for this thing, and GraalVM is not yet fully supported (various dynamic link libraries are missing on various platforms);
Just a project for learning!
the code
Here is an example of the CheckSum tool provided by the picocli framework:
The Maven configuration of the project is as follows:
<dependencies> <dependency> <groupId>info.picocli</groupId> <artifactId>picocli</artifactId> <version>4.7.1</version> </dependency></dependencies><build> <plugins> <!-- Enabling Annotation Processor --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <!-- annotationProcessorPaths requires maven-compiler-plugin version 3.5 or higher --> <version>3.8.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>info.picocli</groupId> <artifactId>picocli-codegen</artifactId> <version>4.7.1</version> </path> </annotationProcessorPaths> <compilerArgs> <arg>-Aproject=${project.groupId}/${project.artifactId}</arg> </compilerArgs> </configuration> </plugin> </plugins></build>
Mainly picocli dependencies and annotation processing plugins;
code show as below:
cli/picocli/a-checksum/src/main/java/io/github/jasonkayzk/CheckSum.java
package io.github.jasonkayzk;import picocli.CommandLine;import picocli.CommandLine.Command;import picocli.CommandLine.Option;import picocli.CommandLine.Parameters;import java.io.File;import java.math.BigInteger;import java.nio.file.Files;import java.security.MessageDigest;import java.util.concurrent.Callable;@Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0", description = "Prints the checksum (SHA-256 by default) of a file to STDOUT.")public class CheckSum implements Callable<Integer> { @Parameters(index = "0", description = "The file whose checksum to calculate.") private File file; @Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...") private String algorithm = "SHA-256"; @Override public Integer call() throws Exception { // your business logic goes here... byte[] fileContents = Files.readAllBytes(file.toPath()); byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents); System.out.printf("%0" + (digest.length * 2) + "x%n", new BigInteger(1, digest)); return 0; } // this example implements Callable, so parsing, error handling and handling user // requests for usage help or version help can be done with one line of code. public static void main(String... args) { int exitCode = new CommandLine(new CheckSum()).execute(args); System.exit(exitCode); }}
The code of the command line framework is still very easy to understand;
If it is executed in IDEA, the corresponding command line parameters must be configured;
So what if it is packaged into a Jar?
Maven configuration
Specify Jar Package Main List Properties
If we do not use other Maven plugins to package, execute after packaging:
$ java -jar target/a-checksum-1.0-SNAPSHOT.jar \ --algorithm SHA-256 hello.txttarget/a-checksum-1.0-SNAPSHOT.jar中没有主清单属性
At this time, an error will be reported: xxx.jar中没有主清单属性
;
This means that we have not specified the entry method of the Jar package, so this Jar package can only be used as a library and cannot be an Executable Jar;
This problem is because: the MANIFEST.MF file under the META-INF folder in the jar package lacks the definition of the jar interface class) To put it bluntly, there is no class class specified);
Let me explain here that MANIFEST.MF is a manifest file, which is equivalent to the ini configuration file in WINDOWS, which is used to configure some information of the program;
There are two solutions:
1. Manually write and configure META-INF/MANIFEST.MF
We can manually write this configuration file, and then package it when packaging, for example:
Manifest-Version: 1.0Build-Jdk: 1.7.0_67Main-Class: io.github.jasonkayzk.CheckSum
But usually we use Maven plugins to help us generate!
2. Use the maven-jar-plugin
plugin
Add the Maven plugin to the configuration:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <manifest> <mainClass>io.github.jasonkayzk.CheckSum</mainClass> </manifest> </archive> </configuration> </plugin> </plugins></build>
In the above configuration, MainClass is configured as our corresponding CheckSum class;
In this way, Maven will automatically generate the corresponding MANIFEST file in the package phase and put it into the Jar package;
Add dependencies to the Jar package
Our Jar package entry is configured above, and then repackage and execute:
$ java -jar target/a-checksum-1.0-SNAPSHOT.jar \ --algorithm SHA-256 hello.txtException in thread "main" java.lang.NoClassDefFoundError: picocli/CommandLine at io.github.jasonkayzk.CheckSum.main(CheckSum.java:35)Caused by: java.lang.ClassNotFoundException: picocli.CommandLine at java.net.URLClassLoader.findClass(URLClassLoader.java:387) at java.lang.ClassLoader.loadClass(ClassLoader.java:418) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ... 1 more
At this time, an error is still reported: java.lang.NoClassDefFoundError: picocli/CommandLine
;
This is because, usually, the Jar package we package does not contain dependent files; but when we run it as an Executable Jar, our dependencies are missing;
Therefore, we need to put our dependencies into the Jar package, ie uber-jar
(or fat-jar
, fat Jar package);
Maven provides two plugins to solve this problem:
- maven-assembly-plugin;
- maven-shade-plugin;
Both of these can be used to package programs and dependencies into an uber-jar, especially when developing sparkstreaming and flink programs and submitting tasks to yarn!
The difference between the two is:
The maven-assembly-plugin plugin will put dependencies and resource files into the final Jar package, such as properties files, etc. If there are resource files with the same name in both the project and dependencies, conflicts will occur, resulting in the same name in the project The file will not be typed into the final Jar package! If this file is a critical configuration file, it will cause problems!
The maven-shade-plugin does not have such a problem; therefore, when actually developing a project, try to use the maven-shade-plugin!
Let’s look at it separately;
Use maven-assembly-plugin
to package
Add configuration to Maven:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.1</version> <configuration> <!-- get all project dependencies --> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <!-- Main in manifest make executable jar --> <archive> <manifest> <mainClass>io.github.jasonkayzk.CheckSum</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions></plugin>
Then package and execute again:
$ java -jar target/a-checksum-1.0-SNAPSHOT-jar-with-dependencies.jar \ --algorithm SHA-256 hello.txt5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
maven-assembly-plugin
plugin will generate two Jar packages, one contains dependencies (such as a-checksum-1.0-SNAPSHOT-jar-with-dependencies.jar
above), and one does not;
maven-assembly-plugin
plugin is relatively simple to use, let’s look at another plugin;
Use maven-shade-plugin
to package
Join the configuration:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <id>checksum</id> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>io.github.jasonkayzk.CheckSum</Main-Class> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions></plugin>
It should be noted that: <id>checksum</id>
must be configured, otherwise an error will be reported when packaging:
Maven – shade for parameter resource: Cannot find 'resource' in class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer
See:
Execute after repackaging:
$ java -jar target/a-checksum-1.0-SNAPSHOT.jar \ --algorithm SHA-256 hello.txt5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
summary
The above mainly describes how to write and package an Executable Jar, and the way of packaging is still the traditional way of Jar package;
In fact, thanks to the development of GraalVM, it is now possible to directly compile Java to Native, but there are still some pitfalls;
I hope to have the opportunity to write about GraalVM in the future~
appendix
source code:
Reference article:
- http://www.noobyard.com/article/p-nrudzjzq-dn.html
- https://www.modb.pro/db/128812
- https://itecnote.com/tecnote/maven-shade-for-parameter-resource-cannot-find-resource-in-class-org-apache-maven-plugins-shade-resource-manifestresourcetransformer/
This article is reproduced from: https://jasonkayzk.github.io/2023/03/20/%E4%BD%BF%E7%94%A8Java%E7%BC%96%E5%86%99Cli%E5%91%BD% E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7/
This site is only for collection, and the copyright belongs to the original author.