[cs - Spring] Spring시작 로딩 시점에 로직 실행 하는 방법

링딩·2023년 5월 2일
0

Computer Science

목록 보기
43/49

Spring시작 로딩 시점에 로직 실행 하는 방법

출처
출처2에서 관련하여 글을 작성하였습니다.



들어가기 전...

Spring 애플리케이션 시작 중 / 후에 로직을 실행하는 것은 일반적인 시나리오이지만 여러 문제를 일으키는 시나리오다.

IoC의 이점을 얻으려면 당연히 컨테이너로가는 애플리케이션의 흐름에 대한 부분적인 제어를 포기해야한다. 이것이 바로 인스턴스화, 시작시 로직 설정 등에 특별한 주의가 필요한 이유이다.

빈의 생성자에 로직을 포함 시키거나 객체의 인스턴스화 후에 메서드를 호출 할 수는 없다. 우리는 그 과정에서 통제권을 가지고 있지 않다.

@Component
public class InvalidInitExampleBean {

    @Autowired
    private Environment env;

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

생성자가 호출될 때 Spring Bean은 완전히 초기화 되지 않았으며 이로 인해 NullPointerException의 문제가 필드를 초기화 하는 이 과정에서 발생하게 됩니다.

스프링에서는 이러한 상황을 해결하면서 동시에 Spring 시작 로딩 때 로직 실행 하는 방법에 아래와 같은 방법들을 제공합니다.





2-1. @PostConstruct 어노테이션

Javax의 @PostConstruct 어노테이션은 특정 클래스의 메소드에 붙여서 해당 클래스의 객체 내 모든 의존성(Bean) 들이 초기화 직후 한 번 실행되어야하는 메소드에 어노테이션을 작성하는 데 사용할 수 있다 .

주입 할 것이 없더라도 어노테이션이 달린 메서드는 Spring에 의해 항상 실행된다는 점을 명심하자.

@Component
public class PostConstructExampleBean {

    private static final Logger LOG 
      = Logger.getLogger(PostConstructExampleBean.class);

    @Autowired
    private Environment environment;

    @PostConstruct
    public void init() {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}
  • NullPointerException을 발생 시키지 않고 @PostConstruct 어노테이션이 달린 메서드 에서 호출 되었음을 알 수 있다 .
  • init() 메소드는 클래스 내 의존성인 environment 가 초기화 된 직후에 호출되기 때문에 동작이 정상적으로 이뤄진다.



2-2. InitializingBean 인터페이스

이 방식은 어노테이션을 붙이는 대신 InitializingBean 인터페이스afterPropertiesSet() 메소드를 구현한다는 것 말곤
앞서 설명한 @PostConstruct 방식과 유사하게 동작한다.

@Component
public class InitializingBeanExampleBean implements InitializingBean {
 
    private static final Logger LOG 
      = Logger.getLogger(InitializingBeanExampleBean.class);
 
    @Autowired
    private Environment environment;
 
    @Override
    public void afterPropertiesSet() throws Exception {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

2-3. ApplicationListener

이 접근법은 로직을 실행하는 데 사용할 수 있어
앞서 설명한 방식처럼 특정 Bean에 초점을 맞추지 않고 Spring 컨텍스트가 초기화가 완료된 후, 즉 모든 Bean 의 초기화가 완료된 후에 실행되도록 하는 방식이다

ApplicationListener 인터페이스를 구현하는 Bean 을 정의하고 onApplicationEvent() 메소드를 Override 하여, 그 안에 원하는 로직을 작성하면 된다.

@Component
public class StartupApplicationListenerExample implements 
  ApplicationListener<ContextRefreshedEvent> {
 
    private static final Logger LOG 
      = Logger.getLogger(StartupApplicationListenerExample.class);
 
    public static int counter;
 
    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
        LOG.info("Increment counter");
        counter++;
    }
}

2-4. @Bean 의 initMethod 속성

@Bean 어노테이션의 initMethod 속성으로 이 Bean 의 초기화가 완료(의존성이 모두 주입)된 뒤에 실행 되어야 할 Bean 내 메소드의 이름을 지정할 수가 있다.
예를 들어 다음과 같은 Bean 이 있다고 가정하자.

public class InitMethodExampleBean {
 
    private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);
 
    @Autowired
    private Environment environment;
 
    public void init() {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

이 때, @Bean 메소드에 initMethod 속성으로 'InitMethodExampleBean' Bean 내의 메소드인 init() 의 이름을 설정해준다.

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



2-5. Spring Boot 의 CommandLineRunner

스프링 부트는 run() 이라는 콜백 메소드를 가진 CommandLineRunner 라는 인터페이스를 제공한다.

run() 메소드는 Spring application context 의 초기화가 완료된(모든 Bean 이 초기화된) 후에 실행되므로 이 안에 원하는 로직을 작성하면 된다.

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
    private static final Logger LOG =
      LoggerFactory.getLogger(CommandLineAppStartupRunner.class);
 
    public static int counter;
 
    @Override
    public void run(String...args) throws Exception {
        LOG.info("Increment counter");
        counter++;
    }
}

참고로 CommandLineRunner Bean 은 같은 애플리케이션 컨텍스트 내에 여러개를 정의할 수 있으며,
Ordered 인터페이스, 혹은 @Order 어노테이션으로 실행 순서를 정해줄 수도 있다.


  • @Component 가 아니라 다음과 같이 @Configuration@Bean 을 사용한 방식으로도 정의할 수 있다.
@SpringBootApplication
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 
    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("John", "john@domain.com");
            User user2 = new User("Julie", "julie@domain.com");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(user -> System.out.println(user));
        };
    }
}

2-6. Spring Boot 의 ApplicationRunner

스프링 부트는 앞서 언급한 CommandLineRunner 인터페이스 외에 ApplicationRunner 인터페이스도 제공한다.
동일하게 run() 이라는 콜백 메소드를 가지고 있어 이 안에 원하는 로직을 작성하면 된다.

+) 참고로 run() 메소드로 들어오는 문자열들은 커맨드 라인으로 앱을 실행할 때 들어온 명령행 인자들이다.

@Component
public class AppStartupRunner implements ApplicationRunner {
    private static final Logger LOG =
      LoggerFactory.getLogger(AppStartupRunner.class);
 
    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++;
    }
}

CommandLineRunner 와 차이?

run() 메소드의 인자가 String 가 아니라 ApplicationArguments 인데
ApplicationArguments 인터페이스는 보통의 커맨드라인 인자 뿐만 아니라,
옵션 읽어 들일 수 있는 getOptionNames(), getOptionValues() 등의 메소드도 가지고 있다.

profile
초짜 백엔드 개린이

0개의 댓글