on
IoC 컨테이너 8부
스프링 프레임워크 핵심기술을 공부하고 정리하는 포스트입니다.
ApplicationEventPublisher
ApplicationEventPublisher는 옵저버 패턴(observer pattern)의 구현체로 이벤트 기반의 프로그래밍을 할 때 유용한 인터페이스이다. 이것 역시 ApplicationContext의 상위 인터페이스이다.
간단한 코드를 통해서 더 알아보자.
일단 Event가 있다고 가정하자. 스프링 4.2 이전에는 항상 ApplicationEvent라는 클래스를 상속받아야 했다. 이 Event는 빈으로 등록되는 것이 아니다. 원하는 데이터를 담아서 전송할 수도 있고, 또는 이벤트를 발생시킬 소스만 전달할 수도 있다.
package me.gracenam.demospring51;
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
data를 추가하고 Event를 퍼블리쉬해야하는데 Event를 발생시키는 기능을 ApplicationContext가 가지고 있다. AppRunner를 추가하자.
package me.gracenam.demospring51;
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
private int data;
public MyEvent(Object source) {
super(source);
}
public MyEvent(Object source, int data) {
super(source);
this.data = data;
}
public int getData() {
return data;
}
}
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
applicationContext.publishEvent(new MyEvent(this, 100));
}
}
Runner가 추가되었고 Event가 발생할 수 있게 되었는데, 그렇다면 Event를 받아서 처리할 수 있는 Handler는 어떻게 만들어야 할까? EventHanlder는 빈으로 등록이 되어서 처리해야 한다.
package me.gracenam.demospring51;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
}
Handler에서는 전달받은 MyEvent
를 통해서 데이터를 꺼낸다던가 하는 작업을 할 수 있다.
이제 실행을 하면 다음과 같은 순서로 동작할 것이다.
- SpringBoot 실행
- AppRunner 실행
- AppRunner에서 Event를 발생시킴
- 발생된 Event를 등록된 빈 중 MyEventHandler가 받아서 출력함
스프링 4.2 이전에는 이처럼 ApplicationListener
와 같은 특정한 인터페이스를 구현을 했어야 했다. 하지만 4.2 이후부터는 이러한 제약사항이 사라졌기 때문에 MyEvent
에서 ApplicationEvent를 상속받을 필요가 없다.
package me.gracenam.demospring51;
public class MyEvent{
private int data;
private Object source;
public MyEvent(Object source, int data) {
this.source = source;
this.data = data;
}
public Object getSource() {
return source;
}
public int getData() {
return data;
}
}
그리고 이러한 것이 바로 스프링 프레임워크가 추구하는 철학이다. 비침투성. 이 코드에는 스프링 패키지가 전혀 들어있지 않다. 이렇게 깔끔한 코드를 스프링 프레임워크는 추구한다. 프레임워크 코드가 개인 코드에 노출되지 않는 이러한 것이 포조(POJO)이고 포조 기반의 프로그래밍을 할 때 테스트하기 편하고 유지보수 하기 쉬워진다.
Event가 바뀌었으니 Handler로 마찬가지로 더 이상 특정한 클래스를 구현하지 않아도 된다.
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
}
대신 누구한테 이 Event를 전달해야하는지 스프링이 알아야 하므로 빈으로는 등록이 되어야 한다. Event를 처리하는 메서드 위에 @EventListener
라는 애노테이션을 추가해 준다. 메서드 이름은 자유롭게 바꿀 수 있다.
여러 개의 EventHanlder가 있는 경우를 살펴보자.
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AnotherHandler {
@EventListener
public void handler(MyEvent myEvent) {
System.out.println("Another : " + myEvent.getData());
}
}
AnotherHandler를 추가해주었는데, 두 Handler 모두 실행된다. 이 때 순차적으로 실행이 되는데, 여기서 순차적이라는 말은 “순서”는 알 수가 없지만 하나가 실행된 후에 다른 하나가 실행된다는 말이다. 동시에 다른 쓰레드에서 실행이 되는 것이 아니다.
쓰레드를 찍어서 확인해보자.
▶ MyEventHandler
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
}
▶ AnotherHandler
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AnotherHandler {
@EventListener
public void handler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another : " + myEvent.getData());
}
}
둘 모두 main 쓰레드인 것을 확인할 수 있고, 이 떄의 순서는 랜덤인지 아니면 어떠한 순서를 보장하는지는 알 수 없다.
순서가 중요한 경우
순서가 중요한 경우 순차적으로 실행하기 위해 @Order
애노테이션을 사용해서 순서를 정할 수 있다.
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
public class AnotherHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public void handler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another : " + myEvent.getData());
}
}
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
}
Another를 나중에 출력하기 위해서 +2를 해주었다.
비동기적으로 실행
각각 별개의 스레드에서 동작하는 비동기적인 실행을 하고 싶을 땐 @Async
애노테이션을 사용하면 된다.
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
}
package me.gracenam.demospring51;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AnotherHandler {
@EventListener
@Async
public void handler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another : " + myEvent.getData());
}
}
비동기적으로 실행할 때는 각각의 쓰레드에서 따로 동작하고 쓰레드 스케쥴링에 따라서 순서가 정해지기 때문에 @Order
가 더 이상 의미가 없어진다.
물론 저 상태로 실행하면 둘 다 main에서 실행되는데 @Async
애노테이션을 붙인다고 Async하게 동작하는 것은 아니기 때문이다. @EnableAsync
를 사용해야 Async하게 동작한다.
package me.gracenam.demospring51;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class Demospring51Application {
public static void main(String[] args) {
SpringApplication.run(Demospring51Application.class, args);
}
}
스프링이 제공하는 기본 이벤트
스프링에서는 기본적으로 제공하는 ApplicationContext와 관련된 이벤트들이 있다. 이 이벤트들은 ApplicationContext에 관한 것이기 때문에 내부에서 ApplicationContext를 꺼내서 사용할 수도 있다.
▶ ContextRefreshedEvent
ApplicationContext를 초기화했거나 리프레시 했을 때 발생한다.
▶ ContextStartedEvent
ApplicationContext를 start()하여 라이프사이클 번들이 시작 신호를 받은 시점에 발생한다.
▶ ContextStoppedEvent
ApplicationContext를 stop()하여 라이프사이클 번들이 정지 신호를 받은 시점에 발생한다.
▶ ContextClosedEvent
ApplicationContext를 close()하여 싱글톤 빈이 소멸되는 시점에 발생한다.
▶ RequestHandledEvent
HTTP 요청을 처리했을 때 발생한다.
package me.gracenam.demospring51;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았다! 데이터는 " + event.getData());
}
@EventListener
@Async
public void handle(ContextRefreshedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextRefreshedEvent");
}
@EventListener
@Async
public void handle(ContextClosedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextClosedEvent");
}
}
Reference
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021