Shell에 해당하는 글 2

Spring Shell Command 구현

Java|2015. 1. 7. 22:16

개요

HelloWorldCommands 클래스를 통해서 Spring Shell 커맨드 구현 방법을 살펴 보겠습니다.

Marker interface

HelloWorldCommands는 org.springframework.shell.core.CommandMarker 인터페이스를 구현하고 있습니다.

@Component
public class HelloWorldCommands implements CommandMarker {

}

CommandMarker는 마커 인터페이스라서 커맨드 구현체라는 것을 알려주기 위해서 implements 할 뿐입니다. 따라서 구현할 메소드는 없습니다.
@Component annotation은 component-scan을 통해 bean으로 등록되라고 붙여 줬습니다.

CLI Annotation

HelloWorldCommands는 모두 3개의 annotation을 사용하여 쉘에서의 동작을 제어합니다.

CliAvailabilityIndicator

어떤 커맨드가 쉘에서 사용 가능한지를 알려주기 위해서 사용합니다. 이 annotation은 boolean 값을 반환하는 메소드에 붙이며, 이 메소드는 annotation에 명시한 커맨드의 사용 가능 여부를 반환합니다.
HelloWorldCommands에서는 hw simple, hw complex, hw enum 커맨드의 사용 가능 여부를 알려 주기 위해서 사용하였습니다.

    private boolean simpleCommandExecuted = false;

    @CliAvailabilityIndicator({"hw simple"})
    public boolean isSimpleAvailable() {
        //always available
        return true;
    }

    @CliAvailabilityIndicator({"hw complex", "hw enum"})
    public boolean isComplexAvailable() {
        if (simpleCommandExecuted) {
            return true;
        } else {
            return false;
        }
    }

위에서 isSimpleAvailable() 메소드는 언제나 true를 반환하여, hw simple 커맨드가 언제나 사용 가능하다고 알려주고 있습니다.
isComplexAvailable 메소드는 simpleCommandExecuted 변수의 상태에 따라서 hw complex, hw enum 커맨드의 사용 가능 여부를 알려줍니다.

댓글()

Spring Shell 소개

Java|2015. 1. 2. 11:17

소개

스프링 프로그래밍 모델을 기반으로 손쉽게 커맨드라인 애플리케이션(interactive shell)을 만들 수 있도록 도와주는 프로젝트입니다.

홈페이지(GitHub)

https://github.com/spring-projects/spring-shell

샘플 애플리케이션

Spring Shell은 샘플 애플리케이션을 제공합니다. HelloWorld 애플리케이션은 부트스트랩(Bootstrap)/커맨드(Command)/베너(Banner)/프롬프트(Prompt)의 기본 구현 방법을 보여줍니다.
샘플을 통해 Spring Shell을 이해하는게 효과적이기 때문에 이를 통해 Spring Shell 사용 방법을 하나하나 설명토록 하겠습니다.

Clone

먼저 Spring Shell 프로젝트를 Clone합니다.

~$ git clone git@github.com:spring-projects/spring-shell.git

빌드 및 실행

Clone이 완료되면, samples/helloworld 디렉토리로 이동한 후 ./gradlew installApp 명령으로 HelloWorld 샘플을 빌드합니다.

~$ cd spring-shell/samples/helloworld
~/spring-shell/samples/helloworld$ ./gradlew installApp
:compileJava
:processResources
:classes
:jar
:startScripts
:installApp

BUILD SUCCESSFUL

./gradlew -q run 명령 혹은 build/install/helloworld/bin/helloworld 스크립트를 실행하면 HelloWorld 샘플을 실행할 수 있습니다.
build/install/helloworld 디렉토리에는 실행 스크립트(bin/helloworld)와 의존 라이브러리(lib/*.jar)가 모두 들어 있기 때문에 이 디렉토리만 배포하면 누구든 이 애플리케이션을 사용할 수 있게 됩니다.
사실 실행만 원한다면 빌드 없이 ./gradlew -q run 명령만 사용해도 됩니다.

~$ cd spring-shell/samples/helloworld
~/spring-shell/samples/helloworld$ ./gradlew -q run
=======================================
*                                     *
*            HelloWorld               *
*                                     *
=======================================
Version:1.2.3
Welcome to HelloWorld CLI
hw-shell>

구현 상세

HelloWorld는 아래 5개의 클래스로 구현되어 있었습니다.

  • org.springframework.shell.samples.helloworld.Main
  • org.springframework.shell.samples.helloworld.commands.HelloWorldCommands
  • org.springframework.shell.samples.helloworld.commands.MyBannerProvider
  • org.springframework.shell.samples.helloworld.commands.MyHistoryFileNameProvider
  • org.springframework.shell.samples.helloworld.commands.MyPromptProvider

Main 클래스는 org.springframework.shell.Bootstrap 클래스의 main 메소드를 호출하여 애플리케이션을 실행합니다.

    /**
     * Main class that delegates to Spring Shell's Bootstrap class in order to simplify debugging inside an IDE
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        Bootstrap.main(args);
    }

이 때 "/META-INF/spring/spring-shell-plugin.xml" 파일을 이용해 ApplicationContext를 생성합니다. spring-shell-plugin.xml 파일은 다음과 같이 단촐합니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.shell.samples.helloworld.commands" />

</beans>

component-scan을 통해 bean을 찾기 때문에 Main 이외의 모든 클래스는 Component annotation을 달고 있습니다.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyPromptProvider extends DefaultPromptProvider {

    @Override
    public String getPrompt() {
        return "hw-shell>";
    }


    @Override
    public String getProviderName() {
        return "My prompt provider";
    }

}

위의 MyPromptProvider 클래스는 Order annotation 또한 달고 있는데, 이는 혹 클래스 패스에 다른 plugin이 Provider(Banner/Prompt/HistoryFileName) 구현체를 가지고 있더라도 내가 구현한 Provider를 우선하도록(Ordered.HIGHEST_PRECEDENCE) 합니다. Provider에 대해서는 곧 설명합니다.

Provider는 Spring Shell이 쉘을 커스터마이징 할 수 있도록 제공하는 확장 포인트로 모두 org.springframework.shell.plugin.NamedProvider 인터페이스를 상속한 인터페이스입니다.

HelloWorld가 사용하는 구현 클래스는 모두 Spring Shell이 제공하는 각 Provider의 기본 구현체(DefaultXXXProvider)를 상속하고 있습니다. HelloWorld는 총 3개의 Provider 구현 클래스를 가지고 있습니다.

  • MyBannerProvider는 이름이 알려주듯이 배너 정보를 담고 있는데, 이외에 버전정보(getVersion()), 환영 메시지(getWelcomeMessage()) 또한 제공합니다.
  • MyPromptProvider는 프롬프트 모양을 정의(getPrompt())합니다.
  • MyHistoryFileNameProvider는 쉘의 동작 히스토리를 담을 로그파일 이름(my.log)을 정의합니다.

MyHistoryFileNameProvider의 코드는 다음과 같습니다.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyHistoryFileNameProvider extends DefaultHistoryFileNameProvider {

    public String getHistoryFileName() {
        return "my.log";
    }

    @Override
    public String getProviderName() {
        return "My history file name provider";
    }

}

MyBannerProvider의 코드는 다음과 같습니다.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyBannerProvider extends DefaultBannerProvider  {

    public String getBanner() {
        StringBuffer buf = new StringBuffer();
        buf.append("=======================================" + OsUtils.LINE_SEPARATOR);
        buf.append("*                                     *"+ OsUtils.LINE_SEPARATOR);
        buf.append("*            HelloWorld               *" +OsUtils.LINE_SEPARATOR);
        buf.append("*                                     *"+ OsUtils.LINE_SEPARATOR);
        buf.append("=======================================" + OsUtils.LINE_SEPARATOR);
        buf.append("Version:" + this.getVersion());
        return buf.toString();
    }

    public String getVersion() {
        return "1.2.3";
    }

    public String getWelcomeMessage() {
        return "Welcome to HelloWorld CLI";
    }

    @Override
    public String getProviderName() {
        return "Hello World Banner";
    }
}

HelloWorld를 실행하면 배너와 버전 정보, 환영 메시지가 출력된 후 hw-shell> 프롬프트 옆에서 커서가 깜빡입니다.
앞에서 설명했듯이 이 때 출력되는 모든 정보는 MyBannerProvider가 제공하며, hw-shell> 프롬프트는 MyPromptProvider에서 정의하고 있습니다.
그리고 HelloWorld를 실행한 디렉토리를 탐색해 보면 MyHistoryFileNameProvider에서 정의한 my.log 파일이 생성된 것을 확인할 수 있습니다.

=======================================
*                                     *
*            HelloWorld               *
*                                     *
=======================================
Version:1.2.3
Welcome to HelloWorld CLI
hw-shell>

지금까지 Spring Shell에 대한 기본적인 내용과 Provider에 대해 알아봤습니다.
가장 중요한 커맨드 클래스에 대해서는 따로 글을 올리겠습니다.

댓글()