Opret et Java Command Line-program med Picocli

1. Introduktion

I denne vejledning nærmer vi os picocli bibliotek, som giver os mulighed for nemt at oprette kommandolinjeprogrammer i Java.

Vi kommer først i gang ved at oprette en Hello World-kommando. Derefter dykker vi dybt ned i bibliotekets nøglefunktioner ved delvis at gengive git kommando.

2. Hej verdenskommando

Lad os begynde med noget let: en Hello World-kommando!

Første ting først skal vi tilføje afhængighed af picocli projekt:

 info.picocli picocli 3.9.6 

Som vi kan se, bruger vi 3.9.6 version af biblioteket, dog en 4.0.0 version er under opførelse (i øjeblikket tilgængelig i alfatest).

Nu hvor afhængigheden er oprettet, lad os oprette vores Hello World-kommando. For at gøre det, vi bruger @Kommando kommentar fra biblioteket:

@Command (name = "hej", beskrivelse = "siger hej") offentlig klasse HelloWorldCommand {}

Som vi kan se, kan kommentaren tage parametre. Vi bruger kun to af dem her. Deres formål er at give oplysninger om den aktuelle kommando og tekst til den automatiske hjælpemeddelelse.

I øjeblikket er der ikke meget, vi kan gøre med denne kommando. For at få det til at gøre noget, skal vi tilføje et vigtigste metodeopkald bekvemmeligheden CommandLine.run (Runnable, String []) metode. Dette tager to parametre: en forekomst af vores kommando, som således skal implementere Kan køres interface og en Snor array, der repræsenterer kommandoargumenterne (optioner, parametre og underkommandoer):

offentlig klasse HelloWorldCommand implementerer Runnable {public static void main (String [] args) {CommandLine.run (new HelloWorldCommand (), args); } @ Override public void run () {System.out.println ("Hello World!"); }}

Nu, når vi kører vigtigste metode, ser vi, at konsoludgange "Hej Verden!"

Når vi er pakket i en krukke, kan vi køre vores Hello World-kommando ved hjælp af java kommando:

java -cp "pathToPicocliJar; pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand

Uden overraskelse udsender det også "Hej Verden!" streng til konsollen.

3. En konkret brugssag

Nu hvor vi har set det grundlæggende, dykker vi dybt ned i picocli bibliotek. For at gøre det skal vi delvist gengive en populær kommando: git.

Selvfølgelig vil formålet ikke være at implementere git kommandoeradfærd, men at gengive mulighederne for git kommando - hvilke underkommandoer der findes, og hvilke muligheder der er tilgængelige for en ejendommelig underkommando.

Først skal vi oprette en GitCommand klasse, som vi gjorde for vores Hello World-kommando:

@Command public class GitCommand implementerer Runnable {public static void main (String [] args) {CommandLine.run (new GitCommand (), args); } @ Override public void run () {System.out.println ("Den populære git-kommando"); }}

4. Tilføjelse af underkommandoer

Det git kommando tilbyder mange underkommandoer - tilføj, begå, fjern, og mange flere. Vi fokuserer her på tilføje og begå.

Så vores mål her vil være at erklære disse to underkommandoer til hovedkommandoen. Picocli tilbyder tre måder at opnå dette på.

4.1. Bruger @Kommando Kommentar om klasser

Det @Kommando annotation giver mulighed for at registrere underkommandoer via underkommandoer parameter:

@Command (underkommandoer = {GitAddCommand.class, GitCommitCommand.class})

I vores tilfælde tilføjer vi to nye klasser: GitAddCommand og GitCommitCommand. Begge er kommenteret med @Kommando og implementere Kan køres. Det er vigtigt at give dem et navn, da navnene vil blive brugt af picocli at genkende, hvilke underkommando (r) der skal udføres:

@Command (name = "add") offentlig klasse GitAddCommand implementerer Runnable {@Override public void run () {System.out.println ("Tilføjelse af nogle filer til iscenesættelsesområdet"); }}
@Command (name = "commit") offentlig klasse GitCommitCommand implementerer Runnable {@Override public void run () {System.out.println ("Forpligtelse af filer i iscenesættelsesområdet, hvor vidunderligt?"); }}

Således, hvis vi kører vores hovedkommando med tilføje Som argument udgives konsollen "Tilføjelse af nogle filer til mellemstationer".

4.2. Bruger @Kommando Annotering om metoder

En anden måde at erklære underkommandoer på er at skab @Kommando- bemærkede metoder, der repræsenterer disse kommandoer i GitCommand klasse:

@Command (name = "add") public void addCommand () {System.out.println ("Tilføjelse af nogle filer til iscenesættelsesområdet"); } @Command (name = "commit") public void commitCommand () {System.out.println ("Forpligtelse af filer i iscenesættelsesområdet, hvor vidunderligt?"); }

På den måde kan vi direkte implementere vores forretningslogik i metoderne og ikke oprette separate klasser til at håndtere den.

4.3. Tilføjelse af underkommandoer programmatisk

Langt om længe, picocli giver os muligheden for at registrere vores underkommandoer programmatisk. Denne er lidt vanskeligere, da vi er nødt til at skabe en Kommandolinje objekt, der indpakker vores kommando, og tilføj derefter underkommandoer til den:

CommandLine commandLine = ny CommandLine (ny GitCommand ()); commandLine.addSubcommand ("tilføj", ny GitAddCommand ()); commandLine.addSubcommand ("commit", ny GitCommitCommand ());

Derefter skal vi stadig køre vores kommando, men vi kan ikke gøre brug af CommandLine.run () metode længere. Nu er vi nødt til at ringe til parseWithHandler () metode på vores nyoprettede CommandLine objekt:

commandLine.parseWithHandler (ny RunLast (), args);

Vi skal bemærke brugen af RunLast klasse, som fortæller picocli for at køre den mest specifikke underkommando. Der er to andre kommandohåndterere leveret af picocli: RunFirst og Kør alle. Førstnævnte kører den øverste kommando, mens sidstnævnte kører dem alle.

Når du bruger bekvemmelighed metoden CommandLine.run (), det RunLast handler bruges som standard.

5. Administration af indstillinger ved hjælp af @Mulighed Kommentar

5.1. Mulighed uden argument

Lad os nu se, hvordan du tilføjer nogle muligheder til vores kommandoer. Faktisk vil vi gerne fortælle vores tilføje kommando om, at den skal tilføje alle ændrede filer. For at opnå det, vi tilføjer et felt, der er kommenteret med @Mulighed kommentar til vores GitAddCommand klasse:

@Option (names = {"-A", "--all"}) private boolske allFiles; @ Override public void run () {if (allFiles) {System.out.println ("Tilføjelse af alle filer til iscenesættelsesområdet"); } andet {System.out.println ("Tilføjelse af nogle filer til iscenesættelsesområdet"); }}

Som vi kan se, tager kommentaren en navne parameter, der giver de forskellige navne på indstillingen. Derfor kalder tilføje kommando med begge -EN eller -alle vil indstille alle filer felt til rigtigt. Så hvis vi kører kommandoen med indstillingen, vises konsollen "Tilføjelse af alle filer til mellemstationer".

5.2. Mulighed med et argument

Som vi lige har set, evalueres deres tilstedeværelse eller fravær for valgmuligheder uden argumenter altid til a boolsk værdi.

Det er dog muligt at registrere indstillinger, der tager argumenter. Vi kan gøre dette ved blot at erklære vores felt for at være af en anden type. Lad os tilføje en besked mulighed for vores begå kommando:

@Option (names = {"-m", "--message"}) privat strengbesked; @ Override public void run () {System.out.println ("Forpligtelse af filer i iscenesættelsesområdet, hvor vidunderligt?"); if (message! = null) {System.out.println ("Forpligtelsesmeddelelsen er" + besked); }}

Ikke overraskende, når det gives besked valgmulighed, kommandoen viser kommandobeskeden på konsollen. Senere i artiklen vil vi dække, hvilke typer der håndteres af biblioteket, og hvordan man håndterer andre typer.

5.3. Mulighed med flere argumenter

Men hvad nu hvis vi vil have vores kommando til at tage flere beskeder, som det er gjort med den virkelige git begå kommando? Ingen problemer, lad os gøre vores felt til et array eller a Kollektion, og vi er stort set færdige:

@Option (names = {"-m", "--message"}) private String [] meddelelser; @ Override public void run () {System.out.println ("Forpligtelse af filer i iscenesættelsesområdet, hvor vidunderligt?"); if (messages! = null) {System.out.println ("Forpligtelsesmeddelelsen er"); for (String besked: beskeder) {System.out.println (besked); }}}

Nu kan vi bruge besked mulighed flere gange:

begå -m "Min forpligtelse er stor" -m "Min forpligtelse er smuk"

Vi vil dog muligvis kun give muligheden en gang og adskille de forskellige parametre med en regex-afgrænser. Derfor kan vi bruge dele parameter for @Mulighed kommentar:

@Option (names = {"-m", "--message"}, split = ",") private String [] meddelelser;

Nu kan vi passere -m "Min forpligtelse er stor", "Min forpligtelse er smuk" for at opnå det samme resultat som ovenfor.

5.4. Påkrævet mulighed

Nogle gange har vi muligvis en mulighed, der er påkrævet. Det krævet argument, som standard er falsk, giver os mulighed for at gøre det:

@Option (names = {"-m", "--message"}, krævet = true) private String [] meddelelser;

Nu er det umuligt at ringe til begå kommando uden at specificere besked mulighed. Hvis vi prøver at gøre det, picocli udskriver en fejl:

Manglende påkrævet mulighed '--message =' Brug: git commit -m = [-m =] ... -m, --message =

6. Håndtering af positionsparametre

6.1. Fangspositionsparametre

Lad os nu fokusere på vores tilføje kommando, fordi det endnu ikke er meget kraftfuldt. Vi kan kun beslutte at tilføje alle filer, men hvad hvis vi ville tilføje specifikke filer?

Vi kunne bruge en anden mulighed til at gøre det, men et bedre valg her ville være at bruge positionsparametre. Ja, positionsparametre er beregnet til at fange kommandoargumenter, der indtager specifikke positioner og hverken er underkommandoer eller optioner.

I vores eksempel vil dette gøre det muligt for os at gøre noget som:

tilføj fil1 fil2

For at indfange positionsparametre, vi gør brug af @Parameters kommentar:

@Parameters private Listefiler; @Override public void run () {if (allFiles) {System.out.println ("Tilføjelse af alle filer til iscenesættelsesområdet"); } hvis (filer! = null) {files.forEach (sti -> System.out.println ("Tilføjelse af" + sti + "til iscenesættelsesområdet")); }}

Nu vil vores kommando fra tidligere udskrive:

Tilføjelse af fil1 til mellemstationsområdet Tilføjelse af fil2 til mellemstationsområdet

6.2. Optag et undersæt af positionsparametre

Det er muligt at være mere detaljeret om hvilke positionsparametre der skal fanges takket være indeks kommentarens parameter. Indekset er nulbaseret. Således, hvis vi definerer:

@Parameters (index = "2 .. *")

Dette ville fange argumenter, der ikke matcher indstillinger eller underkommandoer, fra den tredje til slutningen.

Indekset kan enten være et interval eller et enkelt tal, der repræsenterer en enkelt position.

7. Et ord om typekonvertering

Som vi har set tidligere i denne vejledning, picocli håndterer en type konvertering i sig selv. For eksempel kortlægges det flere værdier til arrays eller Samlinger, men det kan også kortlægge argumenter til bestemte typer som når vi bruger Sti klasse til tilføje kommando.

Faktisk, picocli leveres med en masse præhåndterede typer. Det betyder, at vi kan bruge disse typer direkte uden at skulle tænke på at konvertere dem selv.

Dog kan det være nødvendigt at kortlægge vores kommandoargumenter til andre typer end dem, der allerede er håndteret. Heldigvis for os, dette er muligt takket være ITypeConverter interface og CommandLine # registerConverter metode, der forbinder en type til en konverter.

Lad os forestille os, at vi vil tilføje config underkommando til vores git kommando, men vi ønsker ikke, at brugerne skal ændre et konfigurationselement, der ikke findes. Så vi beslutter at kortlægge disse elementer til et enum:

public enum ConfigElement {USERNAME ("user.name"), EMAIL ("user.email"); privat endelig strengværdi; ConfigElement (strengværdi) {this.value = værdi; } offentlig strengværdi () {returværdi; } public static ConfigElement from (String value) {return Arrays.stream (values ​​()) .filter (element -> element.value.equals (value)) .findFirst () .orElseThrow (() -> new IllegalArgumentException ("The argumentet "+ værdi +" matcher ikke nogen ConfigElement ")); }}

Plus i vores nyoprettede GitConfigCommand klasse, lad os tilføje to positionsparametre:

@Parameters (index = "0") privat ConfigElement-element; @Parameters (index = "1") privat strengværdi; @Override public void run () {System.out.println ("Indstilling" + element.værdi () + "til" + værdi); }

På denne måde sørger vi for, at brugerne ikke kan ændre ikke-eksisterende konfigurationselementer.

Endelig skal vi registrere vores konverter. Hvad der er smukt er, at hvis vi bruger Java 8 eller højere, behøver vi ikke engang at oprette en klasse, der implementerer ITypeConverter interface. Vi kan bare videregive en lambda eller en metodehenvisning til registerConverter () metode:

CommandLine commandLine = ny CommandLine (ny GitCommand ()); commandLine.registerConverter (ConfigElement.class, ConfigElement :: fra); commandLine.parseWithHandler (ny RunLast (), args);

Dette sker i GitCommand hoved () metode. Bemærk, at vi var nødt til at give slip på bekvemmeligheden CommandLine.run () metode.

Når det bruges med et uhåndteret konfigurationselement, viser kommandoen hjælpemeddelelsen plus et stykke information, der fortæller os, at det ikke var muligt at konvertere parameteren til en ConfigElement:

Ugyldig værdi for positionsparameter ved indeks 0 (): kan ikke konvertere 'user.phone' til ConfigElement (java.lang.IllegalArgumentException: Argumentet user.phone matcher ikke nogen ConfigElement) Anvendelse: git config 

8. Integrering med Spring Boot

Lad os endelig se hvordan Springify alt dette!

Faktisk arbejder vi muligvis i et Spring Boot-miljø og vil drage fordel af det i vores kommandolinjeprogram. For at gøre det, vi skal skabe et SpringBoot-applikationgennemførelse af CommandLineRunner interface:

@SpringBootApplication public class Application implementerer CommandLineRunner {public static void main (String [] args) {SpringApplication.run (Application.class, args); } @ Override offentlig ugyldig kørsel (String ... args) {}}

Plus, lad os kommentere alle vores kommandoer og underkommandoer med foråret @Komponent kommentar og autowire alt det i vores Ansøgning:

privat GitCommand gitCommand; privat GitAddCommand addCommand; privat GitCommitCommand commitCommand; privat GitConfigCommand configCommand; offentlig applikation (GitCommand gitCommand, GitAddCommand addCommand, GitCommitCommand commitCommand, GitConfigCommand configCommand) {this.gitCommand = gitCommand; this.addCommand = addCommand; this.commitCommand = commitCommand; this.configCommand = configCommand; }

Bemærk, at vi var nødt til at autoledre hver underkommando. Desværre skyldes det, for nu picocli er endnu ikke i stand til at hente underkommandoer fra Spring-sammenhængen, når de erklæres erklærende (med annoteringer). Således bliver vi selv nødt til at gøre denne ledningsføring på en programmatisk måde:

@ Override public void run (String ... args) {CommandLine commandLine = ny CommandLine (gitCommand); commandLine.addSubcommand ("tilføj", addCommand); commandLine.addSubcommand ("commit", commitCommand); commandLine.addSubcommand ("config", configCommand); commandLine.parseWithHandler (ny CommandLine.RunLast (), args); }

Og nu fungerer vores kommandolinjeprogram som en charme med Spring-komponenter. Derfor kunne vi oprette nogle serviceklasser og bruge dem i vores kommandoer og lade Spring tage sig af afhængighedsinjektionen.

9. Konklusion

I denne artikel har vi set nogle nøglefunktioner i picocli bibliotek. Vi har lært, hvordan man opretter en ny kommando og tilføjer nogle underkommandoer til den. Vi har set mange måder at håndtere valgmuligheder og positionsparametre på. Plus, vi har lært, hvordan vi implementerer vores egen type konvertere for at gøre vores kommandoer stærkt skrevet. Endelig har vi set hvordan vi kan bringe Spring Boot ind i vores kommandoer.

Selvfølgelig er der mange ting mere at opdage om det. Biblioteket leverer komplet dokumentation.

Hvad angår den fulde kode for denne artikel, kan den findes på vores GitHub.


$config[zx-auto] not found$config[zx-overlay] not found