spring / / 2023. 11. 20. 20:25

스프링 부트 시작 시 로직 실행하기

스프링 애플리케이션 시작 시 로직을 수행하는 방법이다.

1. 시작 시 로직 수행하기

스프링 애플리케이션 시작동안 로직을 수행하는 것은 일반적인 시나리오이다. 하지만 여러가지 문제를 만나게 될 수도 있다.

IOC(제어의 역전)의 다양한 장점을 얻기 위해 컨테이너에 애플리케이션의 흐름을 맞길 필요가 있다.

프로세스 과정을 통제하기 어렵기 때문에 객체 초기화 후에 빈 생성자나 메소드 호출에 로직을 포함할 수가 없다.

다음 예제를 보자.

@Component
public class InvalidInitExampleBean {

    @Autowired
    private Environment env;

    public InvalidInitExampleBean() {
        env.getActiveProfiles();
    }
}

여기에 생성자에서 autowired 필드를 접근하려고 한다. 생성자가 호출될 때 스프링 빈은 아직 초기화가 되지 않았다. 초기화되지 않은 필드를 호출하는 것은 NullPointerException을 발생시키기 때문에 문제가 될 수 있다.

스프링에서 이런 경우에 사용할 수 있는 몇 가지 방법을 알아보자.

1.1 @PostConstruct 어노테이션

@PostConstruct 어노테이션은 빈의 초기화 이후에 한번만 실행되는 메소드이다. 스프링은 주입할 것이 없더라도 어노테이션 메소드가 실행될 것이다.

@Slf4j
@Component
@RequiredArgsConstructor
public class PostConstructExampleBean {

    private final Environment environment;

    @PostConstruct
    public void init() {
        log.info("" + Arrays.asList(environment.getDefaultProfiles()));
    }
}

Environment 인스턴스가 주입되었고 NullPointerException 없이 @PostConstruct 메소드에서 호출된다는 것을 알 수 있다.

1.2 InitializingBean 인터페이스

InitializingBean 은 비슷한 방법으로 동작한다. 메소드에 어노테이션을 추가하는 방법 대신에 InitializingBean 인터페이스와 afterPropertiesSet() 메소드를 구현한다.

여기에서 InitializingBean 인터페이스를 사용하여 이전 예제를 구현한다.

@Slf4j
@Component
@RequiredArgsConstructor
public class InitializingBeanExampleBean implements InitializingBean {

    private final Environment environment;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info(Arrays.asList(environment.getDefaultProfiles())+"");
    }
}

1.3 ApplicationListener

스프링 컨텍스트가 초기화된 이후에 로직을 수행할 수도 있다. 그래서 특정 빈에 포커싱을 하지 않는다.

이렇게 하기 위해 ApplicationListener<ContextRefreshedEvent> 인터페이스를 구현하는 빈을 생성한다.

@Slf4j
@Component
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {

    public static int counter;

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("Increment counter");
        counter++;
    }
}

@EventListener 어노테이션을 사용하여 같은 결과를 얻을 수 있다.

@Slf4j
@Component
public class EventListenerExampleBean {

    public static int counter;

    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("Increment counter");
        counter++;
    }
}

경우에 따라 적당한 이벤트를 선택할 수 있다. 이 예제에서는 ContextRefreshedEvent 이벤트를 사용했다.

1.4 @Bean initMethod 속성

빈 초기화 이후 메소드를 실행하려면 initMethod 속성을 사용할 수 있다.

@Slf4j
@RequiredArgsConstructor
public class InitMethodExampleBean {

    private final Environment environment;

    public void init() {
        log.info(Arrays.asList(environment.getDefaultProfiles())+"");
    }
}

여기서 우리는 어떤 인터페이스를 구현하거나 특별한 어노테이션도 사용하지 않았다.

@Bean 어노테이션을 사용하여 빈을 정의할 수 있다.

@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
    return new InitMethodExampleBean();
}

1.5 생성자 주입

생성자 주입을 사용하여 필드를 주입한다면 생성자에서 특정 로직을 포함할 수 있다.

@Slf4j
@Component
public class LogicInConstructorExampleBean {

    private final Environment environment;

    @Autowired
    public LogicInConstructorExampleBean(Environment environment) {
        this.environment = environment;
        log.info(Arrays.asList(environment.getDefaultProfiles())+"");
    }
}

1.6 Spring Boot CommandLineRunner

스프링 부트는 run() 콜백 메소드를 가진 CommandLineRunner 인터페이스를 제공한다. 이는 스프링 애플리케이션 컨텍스트가 초기화된 이후 애플리케이션 시작 시점에 실행된다.

@Slf4j
@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {

    public static int counter;

    @Override
    public void run(String...args) throws Exception {
        log.info("Increment counter");
        counter++;
    }
}

공식 문서에 언급된 것처럼, 여러 개의 CommandLineRunner 빈들이 같은 애플리케이션 컨텍스트내에서 정의될 수 있고 @Ordered 인터페이스 혹은 @Order 어노테이션을 사용하여 우선순위를 지정할 수 있다.

1.7 Spring Boot ApplicationRunner

CommandLineRunner 와 비슷하게, 스프링 부트는 애플리케이션 시작 시점에 실행하기 위해 ApplicationRunner 인터페이스를 run() 메소드와 함께 제공한다.하지만 콜백 메소드에 String 인수를 넘기지 않고 ApplicationArguments 클래스의 인스턴스를 가지고 있다.

ApplicationArguments 인터페이스는 인수를 가져오기 위한 메소드를 가지고 있다.

@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {

    public static int counter;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("Application started with option names : {}",
          args.getOptionNames());
        log.info("Increment counter");
        counter++;
    }
}

2. 매커니즘 결합하기

빈을 완전히 사용하기 위해 위의 매커니즘을 결합하여 사용할 수 있다.

실행 순서는 아래와 같다.

  1. 생성자
  2. @PostConstruct 어노테이션 메소드
  3. InitializingBean*의 *afterPropertiesSet() 메소드
  4. XML에서 init-method로 명시된 초기화 메소드

모든 매커니즘을 결합한 스프링 빈을 만들어보자.

@Slf4j
@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {

    public AllStrategiesExampleBean() {
        log.info("Constructor");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("InitializingBean");
    }

    @PostConstruct
    public void postConstruct() {
        log.info("PostConstruct");
    }

    public void init() {
        log.info("init-method");
    }
}

이 빈을 초기화한다면, 위에서 나타낸 순서에 맞게 아래와 같은 로그를 볼 수 있다.

[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method

참고

https://www.baeldung.com/running-setup-logic-on-startup-in-spring

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유