Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

josolha

CommandLineRunner , ApplicationRunner, @Postconstruct 본문

Spring

CommandLineRunner , ApplicationRunner, @Postconstruct

josolha 2024. 1. 19. 17:08
계기

 

프로젝트를 진행하면서 행정구역 API 를 접근하여 데이터를 DB에 넣는 작업을 하다가

@PostConstruct을 통해서 데이터를 초기에 넣도록 작업을 진행하다가...

CommandLineRunner 와 ApplicationRunner를 알게 되어 정리하게 되었다.


@Postconstruct & @PreDestroy

 

우선 스프링 빈의 이벤트 사이클은 아래와 같이 구성된다.

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료

 

스프링은 의존관계 주입이 완료가 되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공하며

또한 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 해당 과정을 통해서 안전하게 종료작업을 진행하게 된다.

  • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
  • 소멸전 콜백: 빈이 소멸되기 직전에 호출

또한 스프링 빈은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.

  • 인터페이스 (InitializingBean, DisposableBean)
  • 설정정보 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy

최신 스프링에서는 @PostConstruct(초기화 콜백), @PreDestroy(소멸전 콜백) 방법으로 사용하길 권장 하여서

따라서 기존 코드에서 데이터 넣을때  @PostConstruct 사용해서 데이터를 넣는 서비스 로직을 실행했다.

@Component
@RequiredArgsConstructor
public class RegionCodeScheduler {

    private final AdminAreaService adminAreaService;

    // 처음 어플리케이션 시작시 (행정구역 코드 API -> DB)실행
    @PostConstruct
    public void initRegionCodes() {
        adminAreaService.insertRegionCodes();
    }
}

위에 코드는 initRegionCodes가 실행되면 행정구역 API 에 있는 정보들이 db에 들어가도록 진행했다.

 

그러다가 CommandLineRunner 와 ApplicationRunner의 존재를 알게 되었는데~


CommandLineRunner & ApplicationRunner

 

스프링 부트 애플리케이션 구동 시점에 특정 코드 실행 시키기기 위해서 2가지 인터페이스를 제공하고 있다.

앞에서 @Postconstruct & @PreDestroy 와는 다르게 이것들은 부트 어플리케이션 컨텍스트가 완전히 로드되고

호출이 되는 차이점을 가지고 있다.

 

CommandLineRunner

  • 스프링 부트 애플리케이션 구동 시점에 실행되어야 하는 코드가 있고,
    특히 자바의 커맨드 라인 인자(문자열 배열)에 접근할 필요가 있는 경우에 사용된다.
  • CommandLineRunner 인터페이스를 구현한 클래스에서는 run 메소드를 구현해야하고, run 메소드의 매개변수는 커맨드 라인에서 제공된 인자들을 담고 있어, 이를 통해 필요한 작업을 수행할 수 있다

코드로 보게되면

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner 실행됨!");
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

우선 CommandLineRunner를 구현하는 클래스를 생성한다.

 

java -jar myapp.jar arg1 arg2 arg3

위와 같이 커맨드 라인 인자를 입력해서 실행을 하게 되면

 

MyCommandLineRunner 클래스의 run 메소드가 호출되며, arg1, arg2, arg3 인자들이 출력된다.

CommandLineRunner 실행됨!
arg1
arg2
arg3

 

ApplicationRunner

  • CommandLineRunner의 차이점은 인자를 다루는 방식만 다르고 나머지는 같다.
  • 인자를 ApplicationArguments 객체로 제공받아, 보다 구조화된 방식으로 인자를 처리할 수 있다.
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner 실행됨!");

        // 비옵션 인자 접근
        if (args.containsOption("nonOptionArgs")) {
            System.out.println("비옵션 인자:");
            for (String name : args.getNonOptionArgs()) {
                System.out.println(name);
            }
        }

        // 옵션 인자 접근
        System.out.println("옵션 인자:");
        for (String optionName : args.getOptionNames()){
            System.out.println(optionName + " = " + args.getOptionValues(optionName));
        }
    }
}

 

이 코드에서 MyApplicationRunner 클래스는 ApplicationRunner 인터페이스를 구현한다.

run 메소드는 애플리케이션 구동 후 자동으로 호출되며, ApplicationArguments 객체를 매개변수로 받는다.

따라서 이 객체를 사용하여 커맨드 라인에서 전달된 옵션 인자와 비옵션 인자를 구분하여 접근할 수 있다.

 

java -jar myapp.jar --option1=value1 --option2=value2 arg1 arg2

위와 같이 커맨드 라인 인자를 입력해서 실행을 하게 되면

 

MyApplicationRunner 클래스의 run 메소드가 호출되며,

아래와 같이 옵션 인자(option1, option2)와 비옵션 인자(arg1, arg2)가 출력하게 된다.

ApplicationRunner 실행됨!
옵션 인자:
option1 = [value1]
option2 = [value2]
비옵션 인자:
arg1
arg2

 


추가적으로

 

CommandLineRunnerApplicationRunner는 스프링 부트에서 초기화 시점에 특정 작업을 수행하기 위해 사용되며,

@Order 어노테이션을 통해 실행 순서를 지정할 수 있다.

 

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class FirstRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("FirstRunner 실행");
    }
}

@Component
@Order(2)
public class SecondRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("SecondRunner 실행");
    }
}

이 코드에서 FirstRunner@Order(1)로, SecondRunner@Order(2)로 지정되어 있으며,

이는 FirstRunnerSecondRunner보다 먼저 실행됨을 의미한다.

 

@PostConstruct와 @PreDestroy에서의 @Order 사용은 불가능

@PostConstruct와 @PreDestroy는 개별 빈의 초기화 및 소멸과 관련된 메소드이다.

스프링은 이러한 메소드를 빈의 생성 및 소멸 시점에 자동으로 호출하고 이 메소드들은 빈의 내부 로직에 속하므로,

빈 간의 실행 순서를 지정하는 @Order 어노테이션과는 관련이 없다

 

스프링에서 빈의 생성 순서는 주로 빈 간의 의존 관계에 의해 결정된다.

예를 들어, 하나의 빈이 다른 빈에 의존하는 경우, 의존되는 빈이 먼저 생성된다.

 

@Order 어노테이션은 이러한 의존 관계가 아닌, 동일한 유형의 여러 빈(예: 여러 CommandLineRunner 구현체)의 실행 순서를 제어하는 데 사용된다.따라서, @PostConstruct 및 @PreDestroy 메소드에는 @Order 어노테이션을 사용할 수 없다.

이들 메소드의 실행 순서는 해당 빈의 생성 및 소멸 순서에 따라 자동으로 결정된다.

 


결론

 

따라서 내가 진행한 행정구역 코드 데이터를 DB에 삽입하는 작업은 특정 서비스 빈의 초기화 과정의 일부로서,

@PostConstruct를 사용하여 처리하는 것이 적절했다고 생각한다.

 

그리고 애플리케이션 시작 시 특정 조건에 따른 로직 실행이 필요하거나, 커맨드 라인 인자를 기반으로 초기 설정을 조정해야 할 때 에는 CommandLineRunner를 사용하는 것이 적절하며

 

좀더 복잡한 커맨드 라인 인자 처리가 필요하거나, 옵션과 비옵션 인자를 명확히 구분해야 할 때 유용하고

커맨드 라인 인자를 통해 다양한 설정 값을 받아 애플리케이션의 동작을 제어해야 할 경우 ApplicationRunner를 사용하는 것이

적절하다.

 


 

'Spring' 카테고리의 다른 글

[Fitingle] 회고록  (0) 2024.06.10
JUnit  (0) 2024.01.23
Open Session In View(OSIV)  (0) 2023.12.16
@ReponseBody,@RestController,ResonseEntity  (0) 2023.12.14
JPA의 N+1 문제와 해결  (0) 2023.12.08