관리 메뉴

제뉴어리의 모든것

Spring Boot 에서 ApplicationEvent 를 사용하여 비동기 처리하기 본문

Spring Boot

Spring Boot 에서 ApplicationEvent 를 사용하여 비동기 처리하기

제뉴어리맨 2022. 10. 18. 18:24


전체 항목

  • 비동기 처리 흐름의 이해
  • 비동기 처리에 필요한 객체들
  • 비동기 처리 구현
  • 결과

Spring Boot에서는 다양한 방법의 비동기 처리를 지원한다.

그렇다면 비동기 처리란 무엇일까?

동기적이지 않다는 것이다.

그렇다면 동기적이란?

사전적인 의미는 동시에 일어난다는 의미이다.

프로그래밍적으로는 한 쓰레드에서 모든 작업이 동시에 일어난다는 의미이다.

여기서 동시란, 

여러작업이 동시에 시작한다는 개념이 아니라,

여러작업이 한 쓰레드에서 다 처리된다고 생각하는것이 동기, 비동기를 이해하기에 좋다.

 

그럼 다시 한번 비동기에 대해 생각해보자.

비동기란 의미는 뭔가 동시에 일어나지 않는것을 말한다.

그렇다면 프로그래밍적인 의미는 각기 다른 쓰레드에서 작업들이 동시적이지 않게 실행된다는것이다.

비동기 또한

여러작업이 여러 쓰레드에서 각자 처리 된다고 생각하는것이 동기, 비동기를 이해하기에 좋다.

 

 

 

그럼 본격적으로 시작해보자.

 

비동기 처리 흐름의 이해

특정 코드에서 이벤트를 발생시킴 => 퍼블리셔가 해당 이벤트를 발행함 =>

이벤트 리스너가 발생된 이벤트를 넘겨받아 처리

 

 

비동기 처리에 필요한 객체들

  • 이벤트 객체 
  • 퍼블리셔 객체
  • 리스너 객체

 

비동기 처리 구현

  • {프로젝트명} Application 클래스에 비동기 기능을 활성화 시킴을 명시
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync //비동기 처리 사용시 비동기를 활성화 시킴
@EnableJpaAuditing
@SpringBootApplication
public class WiseSayingApplication {
    private static ApplicationContext applicationContext;
    public static void main(String[] args) {
        
        applicationContext = SpringApplication.run(WiseSayingApplication.class, args);

    }
}

@EnableAsync 를 붙여주어야 한다.

 

  • 이벤트 객체
    필요한 이벤트마다 아래의 클래스를 참고하여 생성하면 된다.

- PostEvent

import lombok.Getter;
import org.springframework.context.ApplicationEvent;

@Getter
public class PostEvent extends ApplicationEvent { //ApplicationEvent 를 상속받아서 이벤트 객체로써 만듦. 이벤트 처리에 적용되도록 함

    private String message;

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

 

- PatchEvent

@Getter
public class PatchEvent extends ApplicationEvent { //ApplicationEvent 를 상속받아서 이벤트 객체로써 만듦. 이벤트 처리에 적용되도록 함

    private String message;

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

 

  • 퍼블리셔 객체
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;


@Component
@RequiredArgsConstructor
public class CustomEventPublisher{

    private final ApplicationEventPublisher applicationEventPublisher; //실질적인 퍼블리셔 객체

    public void publish(ApplicationEvent applicationEvent) { //이벤트 객체를 인터페이스로 받아서 다형성 적용
        System.out.println("이벤트 발생");
        applicationEventPublisher.publishEvent(applicationEvent); //이벤트 발행
    }

}

 

  • 리스너 객체
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class GenericEventListener {

	@Async //해당 리스너를 비동기로 처리함
    @EventListener(classes = PostEvent.class) //이벤트 리스너임을 알리는 어노테이션, PostEvent 의 핸들러 메소드
    public void handleEvent(PostEvent postEvent) throws InterruptedException {
    
    	Thread.sleep(5000); //테스트를 위한 5초의 딜레이
        
        System.out.println(postEvent.getMessage());
    }
	
    @Async //해당 리스너를 비동기로 처리함
    @EventListener(classes = PatchEvent.class) //이벤트 리스너임을 알리는 어노테이션, PatchEvent 의 핸들러 메소드
    public void handleEvent(PatchEvent patchEvent) throws InterruptedException {
    
    	Thread.sleep(5000); //테스트를 위한 5초의 딜레이
        
        System.out.println(patchEvent.getMessage());
    }
}

@Async 는 해당 핸들러 메소드를 비동기로 처리하겠다는 의미이다.

 

@EventListener 는 메소드레벨에 붙여서 해당 메소드가 이벤트 리스너 메소드임을 알린다.

그리고 속성은

 

- classes

- condition

- id

- value

 

이렇게 4가지 있으며, 리슨 하는 조건을 설정할 수 있다.

 

참조 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html#classes--

 

 

 

  • 이벤트 적용 코드
@Transactional
@RequiredArgsConstructor
@Service
public class MemberService {

    private final MemberRepository repository;
    private final CustomBeanUtils<Member> beanUtils;
    
    //퍼블리셔 DI
    private final CustomEventPublisher customEventPublisher;
    
    public Member createMember(Member member)  {

        verifyNotExistEmail(member.getEmail());
        Member savedMember = repository.save(member);
        
        //이벤트 발생
        customEventPublisher.publish(new PostEvent(customEventPublisher, "Post 발생"));
        
       
        return savedMember;
    }
    
    
     public Member updateMember(Member member) {

        Member findMember = verifyExistMember(member.getMemberId());
        Member modifiedMember = beanUtils.copyNotNullProperties(findMember, member);

        Member updatedMember = repository.save(modifiedMember);


        customEventPublisher.publish(new PatchEvent(customEventPublisher, "Patch 발생"));
        return updatedMember;
    }
}

 

 

 

결과

따로 결과 출력 화면같은것은 없지만

리스너의 핸들러 메소드에서의 5초의 딜레이와는 별개로 

createMember 메소드는 딜레이 되지 않고 정상적으로 종료된다.

왜냐하면 이벤트 발생을 하고 리스너가 처리를 비동기적으로 하기 때문이다.

 

 

 

 

 


참조 : 

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html#classes--

 

EventListener (Spring Framework 5.3.23 API)

The event classes that this listener handles. If this attribute is specified with a single value, the annotated method may optionally accept a single parameter. However, if this attribute is specified with multiple values, the annotated method must not dec

docs.spring.io

https://daddyprogrammer.org/post/14625/spring-boot-events/

https://private.tistory.com/m/24