관리 메뉴

개발그래머

ApplicationRunner, CommandLineRunner를 활용하여 운영 효율 높이기 본문

Spring

ApplicationRunner, CommandLineRunner를 활용하여 운영 효율 높이기

임요환 2024. 3. 31. 19:49

개요

  • 서비스를 운영하다 보면 간헐적으로 요청 오는 정보(ex. 코드로 처리하면 편리한 경우)
  • 작업들 또는 암호화 / 복호화 같은 애플리케이션 내부에서만 처리할 수 있는 작업(ex. 유저의 복호화된 전화번호 정보가 담긴 엑셀파일이 필요하다)
  • 해당 유저 혹은 작업에 대한 복합적인 여러 정보들을 쉽게 확인할 수 있도록 하는 작업(ex. 여러 테이블들에 있는 정보들이 필요하다)
  • 위의 운영 작업들을 효율적으로 처리하기 위해 ApplicationRunner와 CommandLineRunner를 활용한 방법들을 소개합니다.

ApplicationRunner와 CommandLineRunner 인터페이스란?

  • 스프링부트 애플리케이션을 시작할 때 어떤 동작을 수행하도록 지원하는 인터페이스이며 Runner 인터페이스를 상속받고 있습니다.
  • 스프링부트 애플리케이션이 시작할때 필요한 초기화 작업을 처리하기 위해 사용됩니다.
  • ApplicationRunner와 CommandLineRunner의 차이점은 run 메서드의 매개변수 타입이 다릅니다.
  • ApplicationRunner는 ApplicationArgument를 사용하고 CommandLineRunner는 단순한 String 배열을 받습니다.
interface Runner {

}


class CustomCommandLineRunner : CommandLineRunner {
    override fun run(vararg args: String?) {
        TODO("Not yet implemented")
    }
}

class CustomApplicationRunner : ApplicationRunner {
    override fun run(args: ApplicationArguments?) {
        TODO("Not yet implemented")
    }
}
  • Runner 인터페이스는 스프링부트 애플리케이션이 run을 마무리할 때쯤에 callRunners()를 호출하며 실행됩니다.
public ConfigurableApplicationContext run(String... args) {
    ...
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        startup.started();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
        }
        listeners.started(context, startup.timeTakenToStarted());
        callRunners(context, applicationArguments); // 이부분
    }
    catch (Throwable ex) {
        if (ex instanceof AbandonedRunException) {
            throw ex;
        }
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    ...
    return context;
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    context.getBeanProvider(Runner.class).orderedStream().forEach((runner) -> {
        if (runner instanceof ApplicationRunner applicationRunner) {
            callRunner(applicationRunner, args);
        }
        if (runner instanceof CommandLineRunner commandLineRunner) {
            callRunner(commandLineRunner, args);
        }
    });
}

CommandLineRunner 인터페이스 활용

@Component
class CustomCommandLineRunner(
    private val userExcelExporter: UserExcelExporter
) : CommandLineRunner {

    override fun run(vararg args: String?) {
        if (args.isEmpty()) return
        
        if (args[0].equals("createUserExcel")) {
            userExcelExporter.export(args)
        }
    }
}
  • CommandLineRunner 인터페이스를 구현하게 되면 run 메서드를 override 하여야 합니다.
  • 원하는 작업의 네이밍, 파라미터 등의 컨벤션을 정하고 코드를 작성하면 됩니다.
  • 실행은 스프링부트 애플리케이션을 실행하는 것과 동일하게 실행하면 됩니다.
java -jar RunnerApplication.jar createUserExcel 20230203
  • spring-boot-starter-web 라이브러리를 사용하는 경우 application.yml에 tomcat 서버로 실행되지 않도록 해주어야 합니다.
spring:
  main:
    web-application-type: none

결론

  • 기존 스프링을 사용하는 것처럼 사용할 수 있습니다.
  • 멀티 모듈을 활용하면 기존 엔티티나 쿼리 등을 활용할 수 있습니다.
  • 운영을 하다보면 비정기적으로 요청하는 작업들 혹은 정기적으로 요청하는 작업이어도 개발자를 거쳐야 하는 작업들에 사용하기 편리합니다.
  • 엑셀 파일을 읽어 DB에 데이터를 적재해줘야하는 경우나 DB에 있는 데이터를 엑셀로 출력해야 하는 경우 사용하기 용이합니다.