본문 바로가기

더 나은 엔지니어가 되기 위해/지금은 안쓰는 자바

[스프링 프레임워크 핵심 기술] IoC 컨테이너 3

인프런에서 백기선님의 스프링 프레임워크 핵심 기술 을 공부하며 개인적으로 정리한 글입니다.

MessageSource

MessageSource 객체는 다국어에 관련된 메시지 포매팅을 해주는 역할을 담당한다.
다음 예를 살펴보면 쉽게 이해가 간다.

먼저 다음과 같은 프로퍼티 파일들을 정의하자.

# messages.properties

greeting = Hello, {0}
# messages_ko_KR.properties

greeting = 안녕, {0}

이후 다음과 같은 Runner 를 만들자.

@Component
public class AppRunner implements ApplicationRunner {

  @Autowired
  MessageSource messageSource;

  @Override
  public void run(ApplicationArguments args) throws Exception {
    System.out.println(
      messageSource.getMessage("greeting", new String[]{"heumsi"}, Locale.KOREA))
      );
    System.out.println(
      messageSource.getMessage("greeting", new String[]{"heumsi"}, Locale.getDefault())
      );
  }
}

실행하면 다음과 같은 결과가 뜬다.

안녕, 흠시
Hello, heumsi

messageSource.getMessage("key", String[] arguments, Locale.값) 이 들어가는 것을 알 수 있다.

Event

이벤트를 정의하고, 리스닝하고, 발생시키는 일련의 테스크를 설정할 수 있다.

1) 이벤트 정의

스프링 4.2 이전

스프링 4.2 버전 이전에는 다음과 같이 ApplicationEvent 를 상속받아야 한다.

public class MyEvent extends ApplicationEvent {
  private int data; // 실어 보내고자 하는 데이터
  private Object source;

  public MyEvent(Object source) {
    super(source);
  }

  public MyEvent(Object source, int data) {
    super(source);
    this.source = source;
    this.data = data;
  }

  public int getSource() {
    return source;
  }

  public int getData() {
    return data;
  }
}

스프링 4.2 이후

4.2 이후 버전에서는 Spring 관련 코드들이 모두 필요없다.
(프레임워크의 코드가 내 코드에 들어가지 않게 한다. 스프링이 추구하는 철학임.)

public class MyEvent {
  private int data; // 실어 보내고자 하는 데이터
  private Object source;

  public MyEvent(Object source, int data) {
    super(source);
    this.source = source;
    this.data = data;
  }

  public int getSource() {
    return source;
  }

  public int getData() {
    return data;
  }
}

2) 핸들러(리스너) 정의

스프링 4.2 이전

ApplicationListener 를 구현해야 함.

@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {

  @Override
  public void onApplicationEvent(MyEvent myEvent) {
    System.out.println("이벤트를 받았다. 데이터는 " + myEvent.getData());
  }
}

스프링 4.2 이후

ApplicationListener 를 구현할 필요가 없다.
다만, @EventListener 를 추가해줘야 함.
메쏘드 이름도 마음대로 커스터마이징 가능하다.

@Component
public class MyEventHandler{

  @EventListener
  public void handle(MyEvent myEvent) {
    System.out.println("이벤트를 받았다. 데이터는 " + myEvent.getData());
  }
}

3) 이벤트 발생

@Component
public class AppRunner implements ApplicationRuuner {

  @Autowired
  AppilcationEventPublisher publishEvent;

  @Override
  public void run(ApplicationArguments args) throws Exception {
    // 이벤트 발생시키기
    publishEvent.publishEvent(new MyEvent(this, 100));
  }
}

실행시키면 다음과 같이 출력된다.

이벤트를 받았다. 데이터는 100

4) 이벤트 핸들러가 여러개인 경우

다음과 같이 핸들러가 두 개인 경우 한 쓰레드에서 순차적으로 실행한다.
예를 들면 다음과 같이 2개의 핸들러가 있다고 하자.

@Component
public class MyEventHandler{

  @EventListener
  public void handle(MyEvent myEvent) {
    System.out.println(Thread.currentThread().toString());
    System.out.println("MyEventHandler " + myEvent.getData());
  }
}
@Component
public class AnotherEventHandler{

  @EventListener
  public void handle(MyEvent myEvent) {
    System.out.println(Thread.currentThread().toString());
    System.out.println("AnotherEventHandler " + myEvent.getData());
  }
}

이전과 동일한 Runner 를 실행하면, 다음과 같이 main 쓰레드에서 순차적으로 리스너가 실행되는 것을 알 수 있다.

Thread[main,5,main]
MyEventHandler 100
Thread[main,5,main]
AnotherEventHandler 100

만약 리스너의 순서를 부여하고 싶으면 @Order 를 핸들러 메쏘드 위에 붙여주면 된다.
값이 낮을수록 우선순위가 높다.

@Component
public class AnotherEventHandler {

  @EventListener
  @Order(Ordered.HIGHEST_PRECEDENCE + 2)
  public void handle(MyEvent myEvent) {
    ...
  }
}
@Component
public class MyEventHandler {

  @EventListener
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public void handle(MyEvent myEvent) {
    ...
  }
}

이제 다시 실행하면 AnotherEventHandlerhandle 이 먼저 실행되는 것을 알 수 있다.

Thread[main,5,main]
AnotherEventHandler 100
Thread[main,5,main]
MyEventHandler 100

5) 이벤트 핸들러 async

해당 내용은 pass.

ResourceLoader

ResourceLoader리소스 관련 파일에 접근 및 로드하는 역할을 한다.
다음 예를 통해 쉽게 알 수 있다.

먼저 리소스 파일 하나를 생성하자.

# text.txt

hello spring

Runner 에 다음과 같이 ResourceLoader 를 사용하자.

@Component
public class AppRunner implements ApplicationRuuner {

  @Autowired
  ResourceLoader resourceLoader;

  @Override
  public void run(ApplicationArguments args) throws Exception {
    // 리소스 로드하기
    Resource resource = resourceLoader.getResource("classpath:text.txt");

    // 리소스 정보 출력하기
    System.out.println(resource.exists());
    System.out.println(resource.getDescription());
    System.out.println(Files.readString(Path.of(resource.getURL())));
  }
}

실행하면 다음과 같이 출력된다.

true
class path resource [text.txt]
hello spring