Java CLI Application 개발하기

쌈지몬·2022년 9월 15일
2

명령줄 인터페이스(영어: command-line interface, CLI, 커맨드 라인 인터페이스) 또는 명령어 인터페이스는 가상 터미널 또는 터미널을 통해 사용자와 컴퓨터가 상호 작용하는 방식을 뜻한다. 즉, 작업 명령은 사용자가 컴퓨터 키보드 등을 통해 문자열의 형태로 입력하며, 컴퓨터로부터의 출력 역시 문자열의 형태로 주어진다. 일반 사용자들은 GUI (Graphic User Interface)를 선호하는 경향이 있지만 모든 작업 명령을 그래픽 콘솔과 호환시키는데는 UX 기획에서부터 실제 UI 개발까지 많은 공수가 발생할 뿐더러 효율적인 프로세스라고 보기에도 힘들다. 더군다나 Administrator 전용 명령줄 도구와 같이 특수한 경우를 위해 활용되는 CLI가 존재할 수 있기 때문에 CLI에 대한 개발을 필요하다고 볼 수 있다. 이번 포스팅에서는 Java 기반의 CLI Appication을 개발하고 이를 적용하기 위한 테스트 방법을 소개하고자 한다.

1. Java CLI 개발 필요성

사용자가 실행시킨 명령을 그대로 실행하는 CLI의 특성 상 CLI의 개발 언어는 스크립트 기반 언어 (e.g. bash, python)나 저급 언어 (e.g. C, C++)를 사용하는 경우가 많다. 하지만 Java 기반의 CLI를 개발하는 경우는 상대적으로 드물다고 볼 수 있는데 이는 Java 언어의 특성상 Java Application을 실행하기 위해서는 최소한의 환경 (jre)의 환경이 구성되어 있어야하기 때문이다. 이는 해당 CLI를 실행하는 환경에 제약이 발생한다는 것이고 몇 MB도 안될 CLI 실행을 위해 그보다 큰 Java 패키지 설치가 필요한 것은 배보다 배꼽이 더 큰 상황이 될 수도 있다. 그럼에도 Java CLI가 필요한 경우가 있을 수 있는데 이는 내부적으로 실행되는 명령이 java api에 dependency가 있는 경우이다. 특히 스스로가 개발한 Java 기능을 사용하고 싶지만 해당 기능을 서비스 형태로 제공하고 싶지 않은 경우 Java CLI 개발을 통해 외부 API 노출을 막고 스스로만 필요한 기능을 사용할 수 있게 설계가 가능하다.

2. PicoCLI - Java CLI Framework

앞서 Java 기반 CLI의 개발이 필요한 경우에 대해서 언급하였다. 그럼 Java를 통해 CLI를 개발할 때 고려해야할 사항은 무엇일까? 우선 해당 CLI를 실행하였을 때 필요한 옵션값을 정의하고 해당 옵션값이 누락되거나 혹은 잘못 입력되었을 때 예외 처리가 필요할 것이다. 뿐만 아니라 CLI의 사용자 경험을 위해 가능하면 콘솔 상 출력 문자열에 색상이나 스타일을 넣어서 출력하도록 구성할 필요도 있다. 앞서 얘기했던 사항 외에 고려해야할 몇몇 사항들을 고려하여 CLI를 개발해야할까? CLI의 기능 개발도 해야되는데? 다행히도 오픈소스 진영에서는 이러한 사항을 고려하여 Java 기반의 CLI 개발에 필요한 기본적인 기능을 포함한 PicoCLI라는 프로젝트가 개발되고 있다.
Picocli는 코드가 거의 없는 Java 명령줄 응용 프로그램을 만들기 위한 단일 파일 프레임워크이다. Picocli는 JVM 안팎에서 실행할 수 있는 풍부한 명령줄 응용 프로그램을 만드는 가장 쉬운 방법을 목표로 하고 있다. 중첩된 하위 명령의 모든 수준에 대해 사용 가능한 옵션, 옵션 매개변수 및 하위 명령을 표시하는 명령줄 TAB 완성 기능 외에 ANSI 기반 색상, 스타일 기능 지원 등 CLI 개발 시 고려해야할 자잘자잘한 기능들을 제공해주고 있다.

3. Spring 기반 Java CLI 개발

Spring과 PicoCli를 사용하여 간단한 CLI 애플리케이션을 만들려면 기본적으로 세 가지 구성 요소가 필요하다. (Spring을 사용하는 이유는 해당 CLI의 기능 확장을 고려했을 때 편의성을 위함이며 굳이 Spring 없이도 picocli를 통한 CLI 개발이 가능하다!)

  • Spring Boot Application File: 이 파일은 모든 SpringBoot 애플리케이션에 공통
  • A Runner: 모든 매개변수, 옵션 및 값을 PicoCli의 명령에 전달하는 브리지로 사용되는 CommandLineRunner.
  • Command: 이것은 옵션, 하위 명령 및 값이 있는 실제 명령

(1) Application File

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PicocliApplication {

    public static void main(String[] args) {
        SpringApplication.run(PicocliApplication.class, args);
    }

}

(2) Command Line Runner File

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.stereotype.Component;
import picocli.CommandLine;
import picocli.CommandLine.IFactory;

@Component
public class Runner implements CommandLineRunner, ExitCodeGenerator {

  // auto-configured to inject PicocliSpringFactory
  private final IFactory factory;

  private final Command myCommand;

  private int exitCode;

  public Runner(IFactory factory, Command myCommand) {
    this.factory = factory;
    this.myCommand = myCommand;
  }

  @Override
  public void run(String... args) throws Exception {
    exitCode = new CommandLine(myCommand, factory).execute(args);
  }

  @Override
  public int getExitCode() {
    return exitCode;
  }
}

Command File

import java.util.concurrent.Callable;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import picocli.CommandLine;
import picocli.CommandLine.Option;

@Component
@CommandLine.Command(name = "MyRefactorCLI", mixinStandardHelpOptions = true, version = "myrefactor-cli-1.0", description = "CLI Project")
public class Command implements Callable<Integer> {

  @Option(names = { "-a",
      "--address" }, paramLabel = "API_ADDRESS", description = "The address you want to connect to")
  String option;

  private RestTemplate restTemplate = new RestTemplate();

  @Override
  public Integer call() throws Exception {
    System.out.println("Executing option -a: " + this.option);
    return 0;
  }

}

build.gradle

plugins {
	id 'org.springframework.boot' version '2.7.3'
	id 'io.spring.dependency-management' version '1.0.13.RELEASE'
	id 'java'
}

group = 'com.tmax'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'info.picocli:picocli-spring-boot-starter:4.6.3'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

4. 컴파일 및 실행

(1) Jar 파일 생성

해당 Java Application을 Jar 파일 형태로 생성한다.

gradle clean build jar

(2) Java CLI 실행

해당 Jar 파일을 실행 환경에 옮긴 뒤 java 명령을 통해 CLI 실행한다.

java -jar .\target\xxx.jar -a 10.0.0.0

해당 Java Application 실행 시 java 명령을 뺀 CLI 이름만 가지고 명령을 수행하고 싶을 경우, alias를 통해 Application Packaging이 가능하다.

alias mycommand='java -jar "/path/to/picocli-4.6.3.jar:/path/to/myapp.jar"'

참고

https://ko.wikipedia.org/wiki/%EB%AA%85%EB%A0%B9_%EC%A4%84_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4
https://picocli.info/
http://myrefactor.com/en/posts/spring-boot-and-picocli-example-application

profile
What goes around comes around.

0개의 댓글