Goal
- ApplicationContext의 ApplicationEvent Publisher에 대해 알아본다
- 이벤트 만들어보고 이해해본다
- 이벤트 순서를 보장하는 방법에 대해 알아본다
- 스프링이 제공하는 기본 이벤트에 대해 알아본다
ApplicationEvent Publisher란?
이벤트 프로그래밍에 대한 인터페이스를 제공하며 옵저버 패턴의 구현체다
public interface ApplicationContext extends ApplicationEventPublisher
- publishEvent(ApplicationEvent event)
이벤트 만들기
스프링 4.2 이전
MyEvent
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;
}
}
- ApplicationEvent를 상속받아야 했다.
- 새로 추가해서 데이터를 받아오는 작업도 가능
- private int data; ~
AppRunner
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher publishEvent;
@Override
public void run(ApplicationArguments args) throws Exception {
publishEvent.publishEvent(new MyEvent(this,10));
}
}
- Event를 발생시킬 AppRunner
- ApplicationRunner를 구현했어야 했다.
- bean으로 등록해야 한다
MyEventHandler
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("이벤트 받았다. 데이터는 "+event.getData());
}
}
- event를 받아서 처리할 핸들러
- ApplicationListener를 구현했어야 했다
- bean으로 등록해야 한다
결과
이벤트 받았다. 데이터는 10
정리
=> main함수 -> AppRunner에서 이벤트 발생 -> MyEventHandler가 이벤트를 받아서 처리
스프링 4.2 이후
MyEvent
public class MyEvent {
private int data;
public 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기반의 코드 -> 유지보수 하기 좋은 코드
- ApplicationEvent를 상속받지 않아도 된다
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
System.out.println("이벤트 받았다. 데이터는 "+event.getData());
}
}
- bean으로 등록해야 한다
- @EventListener어노테이션을 사용하고 메서드이름 변경 가능
- ApplicationListener를 구현하지 않아도 된다
결과
이벤트 받았다. 데이터는 10
여기서, 다른 이벤트가 추가되었다고 가정해보자
이벤트가 여러 개 있는 경우
AnotherHandler
@Component
public class AnotherHandler {
@EventListener
public void handle(MyEvent event) {
System.out.println("Another handler " +event.getData());
}
}
결과
Another handler 10
이벤트 받았다. 데이터는 10
=> 기본적으로는 순차적으로 진행(Synchroniezed). 순차적이라 함은 무엇이 우선이 될지는 모르지만, a라는 핸들러 실행 후 b 핸들러 실행. 순서보장 X
동시에 다른 스레드에서 실행한다는 의미 X
스레드를 찍어서 확인해보자
System.out.println(Thread.currentThread().toString());
결과
Thread[main,5,main]
Another handler 10
Thread[main,5,main]
이벤트 받았다. 데이터는 10
순서를 보장하기
우선 순위 @Order(Ordered.HIGHEST_PRECEDENCE)
@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());
}
}
- @Order(Ordered.HIGHEST_PRECEDENCE)를 사용하기
- @Order(Ordered.HIGHEST_PRECEDENCE)+2와 같이 사용할 수 도
결과
Thread[main,5,main]
이벤트 받았다. 데이터는 10
Thread[main,5,main]
Another handler 10
비동기
순서 보장이 없으며, 각각의 스레드풀에서 따로 스케쥴링에 따라 실행되기 때문에 @Order의 의미가 없다.
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 받았다. 데이터는 "+event.getData());
}
}
@Component
public class AnotherHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another handler " +event.getData());
}
}
- 그러나 @Async만 붙힌다고 async하게 동작하지 않음으로 main 스레드에서 실행
결과
Thread[main,5,main]
Another handler 10
Thread[main,5,main]
이벤트 받았다. 데이터는 10
@SpringBootApplication
@EnableAsync
public class Demospring52Application {
public static void main(String[] args) {
SpringApplication.run(Demospring52Application.class, args);
}
}
- @EnableAsync를 붙히면 Async로 동작
- 원래는 스레드 풀에 관한 설정을 더 해줘야 한다.
결과
Thread[task-1,5,main]
Another handler 10
Thread[task-2,5,main]
이벤트 받았다. 데이터는 10
=> 제 각각 별도의 스레드에서 동작했음을 확인할 수 있음
스프링이 제공하는 기본 이벤트
ContextRefreshedEvent, ContextClosedEvent
@Component
public class AnotherHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another handler " +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");
}
}
결과
Thread[task-1,5,main]
ContextRefreshedEvent
2023-07-24T21:10:40.346+09:00 INFO 40707 --- [ main] c.e.d.Demospring52Application : Started Demospring52Application in 1.647 seconds (process running for 2.05)
Thread[task-2,5,main]
Thread[task-3,5,main]
Another handler 10
이벤트 받았다. 데이터는 10
//어플리케이션 종료시
ContextClosedEvent
참고
왜 도대체 나는 ContextClosedEvent가 안 찍히지 이러고 어플리케이션 종료도 하지 않고 기다렸다고 한다.
그러다가 질문창에서 exit code 에 따른 ContextClosedEvent 작동여부라는 걸 봤는데
아래와 같은 답변을 확인했다.
그래서 나두,, 메인 함수에 System.exit(130); 추가하고 자동으로 종료시켜서 확인 완~
답변
윈도에서 UI 기반으로 애플리케이션 (인텔리J) 실행하고 종료할 때 발생하는 exit code가 다른가 봅니다. 이론적으로는 exit code와 상관없이 애플리케이션 종료시 이벤트가 발생해야 할거 같은데 인텔리J에서 버튼을 눌러서 종료하는 방법이 디버그 모드로 실행했는지 애플리케이션 모드로 실행했는지, OS에 따라서도 다른거 같습니다.
exit code 변경하지 않고 jar 파일로 패키징을 한 다음에 java -jar로 실행을 하고 Ctrl+C로 종료 시키니까 ContextClosedEvent 리스너가 제대로 동작하는걸 확인할 수 있었습니다.
또는 ExitCodeGenerator를 구현하는 방법도 있기는 한데 아래 방법도 윈도 인텔리J에서는 디버그 모드로 실행할 때만 동작하네요. 좋은 질문 감사합니다.
참고 코드
@SpringBootApplication
public class DemoExitApplication implements ExitCodeGenerator {
public static void main(String[] args) {
SpringApplication.run(DemoExitApplication.class, args);
}
@Override
public int getExitCode() {
return 130;
}
}
참고
스프링입문 강의 by 백기선
https://www.inflearn.com/course/lecture?courseSlug=spring&unitId=15538 https://www.inflearn.com/course/spring_revised_edition/dashboard
https://www.inflearn.com/course/spring_revised_edition/dashboard
'Spring > about spring' 카테고리의 다른 글
[Resource / Validation] Resource 추상화 (0) | 2023.07.25 |
---|---|
[IoC 컨테이너와 빈] ResourceLoader (0) | 2023.07.24 |
[IoC 컨테이너와 빈] MessageSource (0) | 2023.07.24 |
[IoC 컨테이너와 빈] Environment 프로파일, 프로퍼티 (0) | 2023.07.23 |
[IoC 컨테이너와 빈] 빈의 스코프 (0) | 2023.07.22 |