SpringApplication

main() 메서드에서 시작되는 스프링 어플리케이션의 초기 설정을 제어할 수 있는 클래스이다.
일반적으로는 main() 메서드 안에서 어플리케이션의 실행을 SpringApplication.run() 메서드에게 위임하거나, SpringApplication 객체를 만들어 run() 메서드를 실행할 수 있다.
SpringApplication이 초기화 되면서 어플리케이션이 구동될 때 발생하는 이벤트나 에러시 동작하는 FailureAnalyzer 등에 대해 알아본다.

public static void main(String[] args) {
    SpringApplication.run(MySpringConfiguration.class, args); // static 메서드로 기동
}
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(App.class); // 객체를 생성하여 기동
    app.run(args);
}

FailureAnalyzers

어플리케이션 실행시 오류가 발생한 경우에 동작하며, 에러에 대한 내용과 에러를 고치기 위한 Action을 출력한다.
이미 많은 FailureAnalyzers가 등록되어 있고, 추가도 가능하다.

***************************
APPLICATION FAILED TO START
***************************

Description:
Embedded servlet container failed to start. Port 8080 was already in use.

Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

Application Events and Listeners

스프링에서 제공하는 ContextRefreshedEvent 외에도 스프링부트의 SpringApplication은 추가적인 어플리케이션 이벤트들을 보낸다.
어플리케이션 기동이 시작되면 각 단계에 따라서 ApplicationStartingEvent를 시작으로 이벤트들이 발생하고, 발생된 이벤트들은 이벤트리스너에 전달된다.

각 단계별로 어떤 이벤트가 발생하는지는 아래 스프링부트 공식문서를 참고하자.
https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners

이벤트리스너가 빈으로 등록되어 있고, 리스너의 표적 이벤트가 발생하면 자동으로 처리메서드가 실행된다.
아래 코드와 로그는 빈으로 등록된 ApplicationStartedEvent리스너가 어플리케이션 기동시 출력된 로그이다.
SampleListener 클래스가 @Component 애노테이션으로 빈등록 되었으며, 기동시 ApplicationStartedEvent 이벤트가 발생하면서 자동으로 onApplicationEvent() 메서드가 호출된다.

@Component // 빈으로 등록한다.
public class SampleListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
        System.out.println("ApplicationStartedEvent가 발생!");
    }
}

어플리케이션 기동시 마지막에 콘솔로그가 출력된다. (ApplicationStartedEvent 가 기동시 제일 마지막에 발생하기 때문이다.)

2019-12-26 16:09:49.823  INFO 4212 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-12-26 16:09:49.828  INFO 4212 --- [           main] com.yang.wind.App                        : Started App in 2.1 seconds (JVM running for 3.235)
ApplicationStartedEvent가 발생!

하지만 ApplicationStartingEvent의 경우 ApplicationContext가 초기화 되기 전에 발생하는 이벤트기 때문에, 빈 등록으로는 리스너가 동작하지 않는다. 이런 경우에는 SpringApplicationaddListeners() 메서드로 리스너를 추가하면 된다.
아래 코드는 SpringApplication.run()으로 바로 시작하지 않고, 객체 생성을 통해 설정을 추가했다.

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(App.class);
        app.addListeners(new SampleListener()); // 리스너를 코드로 추가한다.
        app.run(args);
    }
}
// 이미 SpringApplication에 리스너를 등록했으므로 빈등록이 필요없다.
public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("ApplicationStartingEvent 발생!");
    }
}

어플리케이션 기동시 처음에 콘솔로그가 출력된다.

ApplicationStartingEvent 발생!
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)
....

WebApplicationType

SpringApplication에서는 ApplicationContext의 타입이 내부적으로 확정된다.
3개의 타입이 존재하는데 다음과 같이 자동설정되며 SpringApplicationsetWebApplicationType() 메서드를 이용하여 코드에서 특정타입으로 오버라이드가 가능하다.

  • WebApplicationType.SERVLET : Spring MVC가 존재하면 설정됨
  • WebApplicationType.REACTIVE : Spring MVC가 없고, Spring WebFlux가 존재하면 설정됨
  • WebApplicationType.NONE : 위의 경우에 해당이 안되면 설정됨. 어플리케이션이 기동 되었다가 바로 종료된다.
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(App.class);
        // ApplicationType 설정을 오버라이드 한다.
        app.setWebApplicationType(WebApplicationType.SERVLET);
        app.run(args);
    }
}

Accessing Application Arguments

어플리케이션 기동시 설정한 아규먼트에 접근하고 싶다면 org.springframework.boot.ApplicationArguments 빈을 사용하면 된다.

커맨드나 IDE옵션 등으로 프로그램 아규먼트(JVM 옵션이 아니라 프로그램 옵션)를 설정하고 어플리케이션을 구동했다면 아규먼트는 main(String[] args) 메서드에서 SpringApplication으로 전달된다.
SpringApplication 은 아규먼트를 ApplicationArguments 빈에 바인딩하여 원하는 클래스에서 사용할 수 있도록 한다.
즉, 사용하기 원하는 클래스에서 @Autowired나 생성자로 ApplicationArguments 를 주입받아 사용하면 된다. ApplicationArgumentsgetNonOptionArgs(), getOptionNames(), getOptionValues() 등 사용하기 편리하도록 메서드 추상화하여를 제공하고 있다.

@Component
public class MyBean {
    @Autowired
    public MyBean(ApplicationArguments args) { // 생성자에서 초기화된 ApplicationArguments를 주입받는다.
        boolean debug = args.containsOption("debug"); // 제공되는 메서드로 아규먼트에 접근한다.
        List<String> files = args.getNonOptionArgs();
        // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
    }
}

ApplicationRunner / CommandLineRunner

SpringApplication이 시작한 시점에 특정 코드를 실행하고자 한다면, ApplicationRunnerCommandLineRunner를 활용할 수 있다.
두 개의 Runner 인터페이스 중 하나를 선택하여 implements하여 메서드를 오버라이드하고, 빈으로 등록해주면 사전준비는 완료된다.

  • ApplicationRunner
    run(ApplicationArguments args) 메서드 하나만 오버라이드하면 되며 파라미터인 ApplicationArguments에 바인딩된 어플리케이션 아규먼트들에 접근이 가능하다.
    ApplicationArguments는 여러가지 편리한 메서드를 제공하고 있다. 그러므로 아래에서 설명할 CommandLineRunner보다 사용이 권장된다.
    아래는 커맨드로 Option, Non-Option Argument를 설정 후 기동했을 때의 테스트 코드이다.
    java -jar MySpringBootApp.jar alone1 alone2 --key1=value1 --key2=value2

      @Component
      public class SimpleApplicationRunner implements ApplicationRunner {
          @Override
          public void run(ApplicationArguments args) {
              System.out.println("======================================================");
              System.out.println("ApplicationRunner - ApplicationArguments ");
              System.out.println("NonOption Arguments : " + args.getNonOptionArgs());
              System.out.println("Option Arguments Names : " + args.getOptionNames());
              System.out.println("key1의 value : " + args.getOptionValues("key1"));
              System.out.println("key2의 value : " + args.getOptionValues("key2"));
              System.out.println("======================================================");
          }
      }
    
      ======================================================
      ApplicationRunner - ApplicationArguments 
      NonOption Arguments : [alone1, alone2]
      Option Arguments Names : [key1, key2]
      key1의 value : [value1]
      key2의 value : [value2]
      ======================================================
    
  • CommandLineRunner
    ApplicationRunner와 기능은 같지만 전달되는 아규먼트가 더 불친절하다. 그리고 Option과 Non-Option 아규먼트의 구분도 없고 아규먼트도 단순 배열로 전달된다.
    위와 동일한 커맨드로 기동했을 때의 코드와 로그이다. java -jar MySpringBootApp.jar alone1 alone2 --key1=value1 --key2=value2

      @Component
      public class SimpleCommandLineRunner implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("======================================================");
              System.out.println("CommandLineRunner - String... ");
              for(String s : args){
                  System.out.println("argument : " + s);
              }
              System.out.println("======================================================");
          }
      }
    
      ======================================================
      CommandLineRunner - String... 
      argument : alone1
      argument : alone2
      argument : --key1=value1
      argument : --key2=value2
      ======================================================
    

요약하면

  • 스프링부트 어플리케이션의 main 메서드는 SpringApplication 클래스에게 실행을 위임한다.
  • SpringApplication을 통해서 스프링부트의 초기설정을 변경할 수 있다.(FailureAnalyzers, WebApplicationType, EventListener 등)
  • 어플리케이션 기동시 부여한 어플리케이션 옵션을 확인하고 싶다면 ApplicationRunner, CommandLineRunner, ApplicationArguments 중 선택하여 사용한다.