Spring Boot EventHandler 이론

김파란·2024년 7월 12일

SpringAdv

목록 보기
2/8

0. 사용법

  • 회원가입을 하고나서 이메일로 가입을 축하합니다 라는 메시지로 보낸다고 가정
  • 그럴려면 이메일이 필요한데 event를 통해 이메일만 따로 보내서 로직을 작성
  • 의존관계를 가지지 않아도 된다는 장점

1. 개념

  • 이벤트: 프로그램이 감지할 수 있는 특정 사건 또는 행동
  • 이벤트 소스: 이벤트를 발생시키는 객체 또는 컴포넌트
  • 이벤트 리스너: 이벤트가 발생했을 때 실행될 동작을 정의하는 객체
  • js처럼 어떤 이벤트가 발생시킬지 정하는것이다.
  • 이벤트 프로그래밍은 GUI 애플리케이션, 게임개발, 실시간 시스템 등에서 널리 사용
  • 시스템이 사용자나 외부환경의 변화에 즉시 반응할 수 있도록 해준다

1). 구성요소

  • 이벤트 프로듀서: 이벤트를 생성하고 발행하는 컴포넌트
  • 이벤트 컨슈머: 이벤트를 구독하고 처리하는 컴포넌트
  • 이벤트 버스: 이벤트를 전달하고 분배하는 매커니즘

2). 장점

  • 확장성: 시스템 구성 요소간의 느슨한 결합으로 확장성이 높아짐
  • 유연성: 다양한 이벤트 소스와 이벤트 리스너를 쉽게 추가하고 제거할 수 있다
  • 반응성: 이벤트가 발생하면 즉시 반응할 수 있어 실시간 처리에 유리함
  • 유지보수성: 코드의 모듈화가 용이하여 유지보수가 편리함

3). Java와 관계

  • AWT(Abstract Window Toolkit)와 Swing 라이브러리를 통해 이벤트 프로그래밍을 제공
  • Event Object: 이벤트 정보를 담고 있는 객체
  • Event Listener Interface: 이벤트를 처리하기 위해 구현해야 하는 인터페이스
  • Event Source Object: 이벤트를 발생시키는 객체

4). Spring Boot와 관계

  • Event Source: 이벤트를 발생시키는 컴포넌트, 주로 비즈니스 로직내에서 특정 조건이 충족될 때 이벤트 생성
  • Event Listener: 이벤트가 발생했을 때 이를 처리하는 컴포넌트로 특정 이벤트 유형을 감지하고 필요한 작업을 수행
  • Event Publisher: 이벤트를 생성하고 리스너에게 전달하는 역할

(0). ApplicationListner

  • 스프링이 실행될때 실행하고 싶은게 있으면 여기서 구현해도 된다
@Component
@Slf4j
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public boolean supportsAsyncExecution() {
        return ApplicationListener.super.supportsAsyncExecution();
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 스프링 컨테이너가 띄워질때 할일이 있다면 구현해주면 알아서 실행시켜준다
        log.info("애플리케이션 시작");

    }
}

// 시점을 정해둘수 있음
@Component
@Slf4j
public class ShutdownApplicationListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 종료되는 시점에 해야할 일이 있다면, 구현
        log.info("애플리케이션 종료");
    }
}

(1). ApplicationEvent

  • Spring에서 제공하는 기본 이벤트 클래스
  • 이벤트 발생 시간을 기록하며, 이를 통해 이벤트 발생 시점을 추적할 수 있다
public class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

(2). ApplicationEventPublisher

  • 이벤트를 게시(publish)하는 역할, 이벤트를 생성하고 이를 모든 리스너에게 전달
@Service
public class CustomEventPublisher {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void publishEvent(String message) {
        CustomEvent customEvent = new CustomEvent(this, message);
        eventPublisher.publishEvent(customEvent);
    }
}

(3). EventListener

@Component
public class CustomEventListener {

    @EventListener
    public void handleCustomEvent(CustomEvent event) {
        System.out.println("event.getMessage() = " + event.getMessage());

    }
}
@SpringBootApplication
public class EventApplication implements CommandLineRunner {
	@Autowired
	private CustomEventPublisher customEventPublisher;

	public static void main(String[] args) {
		SpringApplication.run(EventApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		customEventPublisher.publishEvent("hello, world");
	}
}

2. 비동기 이벤트 처리

  • @Async를 통해 비동기 이벤트 처리
  • 시간이 오래걸리는 작업, 외부 시스템과의 통신을 비동기로 처리함으로써 유연하게 처리가능
@Configuration
@EnableAsync
public class AsyncConfig {
}

1). Listener

@Component
public class CustomEventListener {

    @EventListener
    @Async
    public void handleCustomEvent(CustomEvent event) throws InterruptedException {
        System.out.println("event.getMessage() = " + event.getMessage());
        // 긴 작업을 비동기로 처리
        try{
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("event.getMessage() = " + event.getMessage());
    }
}

2). @Async 쓰레드풀 지정

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "customTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 기본적으로 유지할 스레드 수
        executor.setMaxPoolSize(10); // 최대 생성할 수 있는 스레드 수
        executor.setQueueCapacity(25); // 작업 큐의 크기
        executor.setThreadNamePrefix("CustomTaskExecutor-"); // 생성되는 스레드 이름에 붙일 접두사
        executor.initialize(); // 메서드 설정 초기화
        return executor;
    }
}

1). Listener

@Component
public class CustomEventListener {

    @EventListener
    @Async("customTaskExecutor") // customTaskExecutor 스레드풀을 사용하도록 지정
    public void handleCustomEvent(CustomEvent event) throws InterruptedException {
        System.out.println("event.getMessage() = " + event.getMessage());
        System.out.println("event.getMessage() = " + Thread.currentThread().getName());

        Thread.sleep(5000); // 이벤트가 발생하면 5초간 현재 스레드 멈춤
    }
}

3. 트랜잭션 이벤트 처리

  • 이벤트를 데이터 일관성을 보장하면서 처리할 수 있도록 해준다
  • 아이템명과 가격만 있다고 생각
  • 트랜잭션 이벤트 처리는 DB작업과 이벤트 처리가 원자적으로 어루어져 신뢰성과 일관성을 높일 수 있다

1). Event Source

@Getter
public class ItemRegistrationEvent extends ApplicationEvent {
    private final String name;
    private final double price;

    public ItemRegistrationEvent(Object source, String name, double price) {
        super(source);
        this.name = name;
        this.price = price;
    }
}

2). Event Listener

@Component
public class ItemRegistrationEventListener {

    @Async("customTaskExecutor")
    @TransactionalEventListener
    public void handleItemRegistrationEvent(ItemRegistrationEvent event) {
        System.out.println("Handing item registration event for item: " + event.getName() + ", price: " + event.getPrice());

    }
}

3). Item Service 계층

@Service
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;
    private final ApplicationEventPublisher applicationEventPublisher;

    @Transactional
    public void registerItem(String name, double price) {
        Item item = new Item(name, price);
        itemRepository.save(item);
        System.out.println("Registering item = " + name);

        // 트랜잭션이 성공적으로 완료된 후 이벤트 게시, 
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                applicationEventPublisher
                        .publishEvent(new ItemRegistrationEvent(this, name, price));
            }
        });

    }
}

4. 스프링 4.2 이후버전

  • ApplicationEvent 상속안받아도 된다
  • 그냥 객체만들어서 사용해도 가능
  • Publisher는 안만들고 그냥 전달해두고 용도니까 따로 클래스 안만들수있으면 안만들자
// Event
@Getter
public class MyEvent {
    private int data;
    private Object source;

    public MyEvent(int data, Object source) {
        this.data = data;
        this.source = source;
    }
}
// Listener
@Component

public class MyEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE) //하나의 이벤트를 처리하는 핸들러가 두개이상일경우 이렇게 순서를 정할 수 있다 
    @Order(Ordered.LOWEST_PRECEDENCE) // (낮을수록 우선순위)
    @Order(1) // (숫자로도 줄수 있음), 비동기에서는 order는 안먹힘
    public void eventHandlerMethod(MyEvent myEvent) {
        System.out.println("myEvent.getData() = " + myEvent.getData());
    }
}

// 실행
@SpringBootApplication
public class EventApplication implements CommandLineRunner {
	@Autowired
	private ItemService itemService;
	@Autowired
	ApplicationEventPublisher applicationEventPublisher;

	public static void main(String[] args) {
		SpringApplication.run(EventApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		applicationEventPublisher.publishEvent(new MyEvent(100, this));
	}
}

1). 스프링에서 제공하는 기본 이벤트

//스프링이 제공하는 기본 이벤트1 - ContextRefreshedEvent
    @EventListener
    public void eventRefreshedHandleMethod(ContextRefreshedEvent event) {
        //어플리케이션 컨택스트가 초기화되거나 리프레쉬 될때 발생하는 이벤트
        System.out.println("초기화 및 리프레쉬 : " +event.getApplicationContext().toString());
    }

    //스프링이 제공하는 기본 이벤트2 - ContextClosedEvent
    @EventListener
    public void eventCloseHandleMethod(ContextClosedEvent event) {
        //어플리케이션 컨택스트가 closed되어 소멸될때 발생하는 이벤트
        System.out.println("컨텍스트 close : " +event.getApplicationContext().toString());
    }

    //스프링이 제공하는 기본 이벤트3 - ContextStartedEvent
    @EventListener
    public void eventStartHandleMethod(ContextStartedEvent event) {
        //어플리케이션 컨택스트를 start()해서 라이프사이클 빈들이 시작 신호를 받으면 발생하는 이벤트
        System.out.println("컨텍스트 start : "+event.getApplicationContext().toString());
    }

    //스프링이 제공하는 기본 이벤트4 - ContextStoppedEvent
    @EventListener
    public void eventHandleMethod(ContextStartedEvent event) {
        //어플리케이션 컨택스트를 stop()하여 라이프사이클 빈이 정지하면 발생하는 이벤트
        System.out.println("컨텍스트 stop : " +event.getApplicationContext().toString());
    }

    //스프링이 제공하는 기본 이벤트5 - RequestHandledEvent
    @EventListener
    public void eventRequestHandleMethod(RequestHandledEvent event) {
        //HTTP 요청을 처리했을 때 발생하는 이벤트
        System.out.println("처리완료");
    }

0개의 댓글