SpringApplication

SpringApplication.run(Application.class, args); // 1
SpringApplication app = new SpringApplication(Application.class, args); // 2

new SpringApplicationBuilder()
    .source(Application.class)
    .run(args); // 3

1번과 같이 스태틱메소드를 이용해서 앱을 실행하면 커스터마이징을 할 수가 없다. 배너를 수정하는 것처럼 커스터마이징이 필요하다면 2, 3번과 같이 직접 애플리케이션을 생성하거나 빌더를 이용한 방법을 사용해야 한다.

참고로 스프링 애플리케이션을 실행하면 디폴트로 로그레벨을 INFO로 설정한다. DEBUG 모드를 설정하면 어떠한 자동설정이 적용되었는지, 자동설정이 왜 적용되지 않았는지 등을 알 수 있다.

FailureAnalyzer

에러가 발생했을 때, 에러 로그를 보기좋게 출력해준다.

배너

앱을 실행할 때 보이는 배너로, src/resources 폴더에 banner.txt 파일을 작성하면 해당 배너가 출력된다. 문서를 보면 해당 파일에서 사용할 수 있는 변수와 설명이 있다. 그 중 일부(application.version 등)는 MANIFEST.MF 파일이 생성이 되어있어야 사용할 수 있다. MANIFEST.MF 파일은 스프링부트를 패키징을 할 때 의존성이 모두 들어있는 jar 하나를 생성하면서 생성된다.

SpringApplication app = new SpringApplication(Application.class, args);
app.setBanner(new Banner() {
   @Ovveride
    public void printBanner(...) {
        out.println("...");
        out.println("...");
    }
});
// app.setBannerModule(Banner.Mode.OFF);

배너는 이미지 파일도 가능하며, properties파일에 spring.banner.location을 이용해서 위치를 지정할 수도 있다. 그 외에도 인코딩 등 여러가지 옵션을 지정할 수 있다. 배너를 끄고자 하는 경우 SpringApplication의 setBannerModule(Banner.Mode.OFF)를 사용할 수 있다. 코드로 배너를 설정할 수도 있는데, 클래스패스에 지정된 파일과 코드로 지정된 배너 중 클래스패스에 지정된 파일의 배너가 우선순위를 갖는다.

이벤트

스프링부터에서 기본적으로 제공해주는 이벤트가 존재한다. 이벤트는 앱 시작, 앱컨텍스트를 만들었을 때, 리프레쉬 됐을 때, 구동이 됐을 때 또는 앱 준비가 됐을 때 등 다양하게 제공된다.

@Component
public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("=======================");
        System.out.println("Application is Starting");
        System.out.println("=======================");
    }
}

ApplicationListener를 만들기 위해서는 ApplicationListener를 구현하면서 Event 타입을 지정하고, onApplicationEvent 메소드를 구현하면 된다.

주의해야 할 점은 이벤트가 발생하는 시점이다. 애플리케이션 컨텍스트가 생성되는 시점을 기준으로, 컨텍스트가 생성된 이후 발생한 이벤트들은 컨테이너의 빈을 실행할 수 있다. ApplicationStartingEvent가 발생하는 시점이 컨텍스트 생성 이후라면 SampleListener 빈을 실행할 수 있다.

하지만 컨텍스트가 만들어지기 이전에 발생한 이벤트는 문제가 된다. ApplicationStartingEvent는 애플리케이션 맨 처음에 발생하므로, 해당 시점에 애플리케이션 컨텍스트가 만들어지지 않았다. 그래서 해당 이벤트가 발생하더라도 SampleListener가 동작하지 않는다.

public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("=======================");
        System.out.println("Application is Starting");
        System.out.println("=======================");
    }
}

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.addListeners(new SampleListener());
        app.run(args);
    }
}

이를 해결하기 위해서는 코드를 수정해야한다. SpringApplication 인스턴스의 addListeners 메소드를 이용하여 SampleListener를 추가해야한다. SampleListener는 빈으로 등록할 필요가 없고, 메인에서 직접 생성하기 때문에 @Component를 지운다. 앱을 실행해보면 컨텍스트 생성 로그가 출력되기 이전에 SampleListener가 실행된다.

@Component
public class SampleListener implements ApplicationListener<ApplicationStartedEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("=======================");
        System.out.println("Started");
        System.out.println("=======================");
    }
}

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.run(args);
    }
}

ApplicationStartedEvent 이벤트는 컨텍스트 생성 로그가 먼저 출력되고 Started 로그가 출력되면서 SampleListener가 실행된다.

WebApplicationType

org.springframework.boot.WebApplicationType 열거형 클래스에는 3가지의 타입이 존재한다. SpringApplication의 setWebApplicationType메소드로 앱 타입을 지정할 수 있다. ServletWebMvc나 SpringMvc가 설정되어있으면 WebApplicationType.SERVLET으로 설정된다. SpringWebFlux가 설정되어있으면 WebApplicationType.REACTIVE으로 설정된다. 둘 다 없다면 WebApplicationType.NONE로 설정된다. SpringMvc와 SpringWebFlux가 모두 들어있다면 WebApplicationType.SERVLET으로 설정된다. 가장 먼저 서블릿의 존재를 체크하기 때문에 WebFlux가 들어있어도 무시된다.

Application Argument

IDE의 실행설정에서 애플리케이션의 인자를 전달할 수 있다. 혹은 직접 jar를 실행할 때도 -- 를 이용하여 인자를 전달한다.

참고로 -D는 VM option이다.

@Component
public class SampleListener  {

    public SampleListener(ApplicationArguments arguments) {
        System.out.println(arguments.containsOption("bar"));
    }
}

ApplicationArguments를 이용해 애플리케이션에 전달된 인자를 사용한다.

애플리케이션이 실행된 후, 특정 작업을 수행

@Component
public class SampleListener implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        
    }
}

애플리케이션이 실행된 후에 특정 작업을 수행하기 위해서는 ApplicationRunner을 구현한 빈을 등록해야한다. run 메소드의 인자는 ApplicationArguments가 전달되며 애플리케이션에 전달된 인자에 접근할 수 있다.

@Component
public class SampleListener implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        
    }
}

CommandLineRunner라는 인터페이스도 있는데, 해당 인터페이스는 인자로 String배열이 전달된다. 백기선 강사님은 ApplicationRunner를 사용하는 것을 추천한다. Runner를 구현한 빈이 여러 개 존재할 경우 @Order를 이용하여 순서를 지정할 수 있다. @Order의 value가 낮을수록 우선순위가 높다.

번외: 독립적으로 실행가능한 Jar

스프링부트에서 빌드된 애플리케이션은 JAR 파일 하나로 애플리케이션을 실행할 수 있다. mvn package나 gradle jar를 실행하면 jar파일이 생성되는데 해당 파일을 java -jar 로 실행하면 스프링부트 애플리케이션이 실행된다.

해당 jar 파일에 대한 압축을 풀어보면, org.springframework.boot 하위에 실행을 위한 Launcher가 존재한다. 과거에는 uber라는 jar를 사용하여 애플리케이션에서 사용하는 모든 jar를 하나로 합쳐서 사용했다. 하지만 결과로 합쳐진 jar는 어떤 라이브러리를 사용하는지 알 수가 없고, 파일 이름이 같은 경우에는 구별할 수가 없었다.

스프링부트는 이러한 단점을 해결하기 위해 JAR 내부에 내장 JAR를 위치하는 전략을 사용했다. 애플리케이션의 위치와 외부 라이브러리의 위치를 구분하여 loader 모듈의 JarFile을 이용해 의존성을 읽어들이고JAR를 실행하기 위한 런처를 사용한다. JAR말고도 WAR와 같은 다른 런처도 존재한다. MENIFEST.MF 파일을 살펴보면 Main-Class가 애플리케이션이 아닌 런처로 지정돼있는 것을 알 수 있다. 해당 런처가 Start-Class인 우리의 애플리케이션을 실행한다.

해당 포스팅은 스프링 부트 개념과 활용 강의 내용을 토대로 작성하였습니다.