본문 바로가기
Java/Spring

[9] 스프링 프레임워크 핵심 - ApplicationEventPublisher, ResourceLoader

by Riverandeye 2020. 12. 14.

ApplicationEventPublisher

 

ApplicationContext가 상속받는 또다른 인터페이스인 ApplicationEventPublisher 를 소개하겠습니다. 

이 인터페이스는 옵저버 패턴의 구현체로

이벤트 기반의 프로그래밍을 할 때 유용합니다. 

 

스프링 4.2 이전의 경우

 

이벤트를 발생시키기 위해서 이벤트 클래스를 선언하되 

ApplicationEvent 를 상속하여 생성자를 오버라이딩 합니다.

 

public class MyEvent extends ApplicationEvent {

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

이 이벤트는 Bean으로 등록되지 않고, 원하는 정보를 담아서 전달해주는 매개라고 생각하면 됩니다. 

이벤트를 발생시키는 지점과 이벤트를 사용하는 지점이 다르기 때문에

발생 지점 -> 사용 지점으로 전달하기 위해선 어떤 매개가 필요한데

이벤트 객체가 그런 매개로서의 역할을 합니다. 

 

이벤트를 Publish 하는 기능을 ApplicationEventPublisher 에서 가지고 있습니다. 

메세지를 발생시키기 위해 ApplicationEventPublisher를 가져와서 이벤트를 발생시켜 봅니다. 

 

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher publisher;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        publisher.publishEvent(new MyEvent(this));
    }
}

그럼 해당 이벤트를 받는 지점은 어디인가요?

ApplicationListener를 Bean으로 등록하여 사용합니다. 

 

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 받았다잉");
    }
}

전달받은 이벤트를 갖고 그 안에 들어있는 데이터를 꺼낸다던가, 특정 작업을 수행해주면 됩니다. 

이렇게 하면 이벤트 기반 프로그래밍을 쉽게 할 수 있습니다. 

 

스프링 4.2 이후

4.2 이후엔 ApplicationEvent를 상속하지 않고도 그냥 Event 객체를 선언하여 사용하면 됩니다. 

public class MyEvent {

    private Object source;
    private String data;

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

Event 객체는 ApplicationEvent를 더이상 상속하지 않아도 되며

 

@Component
public class MyEventListener {

    @EventListener
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 받았다잉");
    }
}

이벤트리스너 또한 ApplicationEventListener를 상속하지 않아도 됩니다.

대신 메소드 위에 @EventListener 어노테이션을 달아주시면 됩니다. (메소드 명도 마음대로)

 

만약 동일한 인자를 받는 이벤트리스너가 여러개 있는 경우엔 모두 실행됩니다.

만약 이벤트 리스너의 순서가 중요한 경우엔 @Order 어노테이션을 이용하면 됩니다. 

 

@Order 어노테이션

Order 어노테이션에 넣는 값이 작을수록 우선순위가 높습니다. 

 

만약 비동기적으로 작동시키려면 @Async 어노테이션을 이벤트 리스너에 추가합니다.

그런 경우엔 자연스럽게 Order가 의미 없게 됩니다. 

@Async 어노테이션만 리스너에 붙인다고 해서 다른 쓰레드에서 비동기적으로 동작하는 것은 아닙니다.

Application 메인 클래스에 @EnableAsync 를 붙여주면 됩니다. 

 

@SpringBootApplication
@EnableAsync
public class AutowiredApplication {
@EventListener
@Async
public void onApplicationEvent(MyEvent event) {
  System.out.println(Thread.currentThread().toString());
  System.out.println("이벤트 받았다잉");
}

 

서로 다른 쓰레드에서 수행되는 중

ResourceLoader

리소스 로더는 인터페이스 이름이 암시하듯 리소스를 로딩해주는 인터페이스인데요

ApplicationContext가 이 기능을 상속해줍니다. 

 

@Autowired
ResourceLoader resourceLoader;

@Override
public void run(ApplicationArguments args) throws Exception {
  Resource resource = resourceLoader.getResource("classpath:test.txt");
  System.out.println(resource.exists());
  System.out.println(resource.getDescription());
  String result = Files.readString(Path.of(resource.getURI()));
  System.out.println(result);
}

단순히 리소스로더를 이용해서 특정 위치의 파일을 가져와서 읽어주는 예시입니다.

classpath의 default 위치는 resources 폴더이니 실제로 텍스트 파일을 생성할 때 resource 폴더에 추가해주세요. 

 

댓글