BackEnd/Spring

Spring Boot main 2배 버그 팝니다@@@@@

Potwings 2024. 9. 18. 21:21

최근 cli로 된 어플리케이션을 개발하였다.

어플리케이션 실행 후 사용할 DB 정보를 입력을 통하여 받고 싶었으나 Spring의 경우 DataSource가 우선 생성되어야 어플리케이션이 실행될 수 있었다.

 

따라서 Spring 구동 이전의 main 메소드에서 DB 정보를 받아오도록 아래와 같이 진행하였다.

 

  public static void main(String[] args) {
    try {

      br = new BufferedReader(new InputStreamReader(System.in));

      String dbUrl = validDBURL(); // IP PORT 연결 테스트
      String schema = validInputString("DB 스키마");
      jdbcUrl = "jdbc:mysql://" + dbUrl + "/" + schema;
      dbUser = validInputString("DB 계정");
      dbPassword = validInputString("DB 패스워드");

      new SpringApplicationBuilder(MainDoubleApplication.class)
          .sources(DataSourceConfig.class)
          .run(args);
    } catch (Exception e) {
      System.err.println("Error: " + e.getMessage() + e);
    }
  }

 

허나 해당 어플리케이션을 실행해보니 Main 메소드의 내용이 2번 실행 후 Spring이 실행되었다.

 

검색을 해보니 매번 아무생각없이 추가한 spring-boot-devtools로 인해 main이 두번 실행되고 있던 것이었다.

 

해당 의존성을 제거하니 정상적으로 Main 메소드 한번 실행 후 Spring이 실행되었다.

 

왜?

그렇다면 spring-boot-devtools는 왜 main 메소드를 두번 실행할까?

이는 devtools의 핵심 기능인 Automatic Restart 기능을 위해서이다.

※ Automatic Restart - 서비스 기동 중 코드 변경사항 반영

 

devtools 공식 문서를 보면 이렇게 나와있다.

https://docs.spring.io/spring-boot/reference/using/devtools.html#using.devtools.restart.restart-vs-reload)

 

정리하자면 아래와 같다.

devtools 사용 시 두 개의 클래스 로더를 사용하여 어플리케이션이 기동된다.
base 클래스로더 - 변경이 발생하지 않는 클래스 관리 (ex. 외부 jar파일)
restart 클래스로더 - 개발을 진행하면서 변경되는 클래스 관리

이후 클래스 변경이 일어나면 기존 restart 클래스로더가 변경된 클래스를 반영한 후 소멸된다. 그 후 새 restart 클래스로더가 생성된다. 
이렇게 하면 base 클래스로더에서 관리하는 변경사항이 발생하지 않는 클래스들은 다시 로딩하지 않으므로 restart 시간이 줄어든다.

 

따라서 빠른 Automatic Restart 진행을 위해 어플리케이션 기동 시 두 개의 클래스 로더를 생성하고 이로 인해 main 메소드가 두번 실행되는 것이다.

 

테스트

main 메소드가 실행되는 것을 보기 위해 실행 시 "Main Method Run"을 출력하도록 해두었고

@SpringBootApplication
public class MainDoubleApplication {

  public static void main(String[] args) {
    System.out.println("================== Main Method Run ==================");
    new SpringApplicationBuilder(MainDoubleApplication.class)
        .run(args);
  }

}

 


Controller에서는 "/test" path호출 시 before change를 반환하도록 해두었다.

@RestController
public class TestController {

  @GetMapping("/test")
    public String test() {
        return "before change";
    }
}

 

초기 실행 후 "/test" 호출 시는 결과는 아래와 같았다.

 

이후 Controller에서 반환하는 내용을 변경하고 기다려 보니 console 창에는 아래와 같은 로그가 발생하였다.

 

Main Method Run이 출력되는 걸 보면 새 restart 클래스로더를 생성하기 위해 main 메소드가 다시 실행되었다는 것을 알 수 있었고

 

이후 "/test"를 호출해보니 Controller의 변경된 내용이 반영되어있었다.

 

결론

spring-boot-devtools에서는 빠른 Automatic Restart 진행을 위해 어플리케이션 기동 시 두 개의 클래스 로더를 생성한다.

클래스 로더 생성 시 main 메소드를 실행해야하고 이로 인해 main 메소드가 두 번 실행되는 것이다.