EventListener

SexyWoong·2023년 11월 15일
0

spring

목록 보기
7/11

프로젝트 내에서 Game기능을 구현하였다. 이를 테스트 하던 중 두명의 사용자가 로그인 후 커플을 맺으면 세션 내에 coupleId가 들어가지 않는 현상을 발견하였다. 이를 팀원들 중 한분이 해결해주셨는데 Security는 아직 잘 모르겠고 EventListener를 사용하여 해결하였길래 이 참에 EventListener를 공부했다.

프로젝트 코드

MemberService.java

@Transactional
    public void updateMemberProfile(List<MultipartFile> profileImages, MemberProfileEditServiceRequest serviceRequest, Long memberId) {
        Member findMember = validateMemberId(memberId);
        String oldProfileImageUrl = findMember.getImageUrl();

        String profileImageUrl = updateProfileImage(profileImages, oldProfileImageUrl);

        updateMemberProfile(profileImageUrl, serviceRequest, findMember);
        eventPublisher.publishEvent(new MemberupdatedEvent(findMember));
    }

SessionInfoUpdateHandler.java

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
   public void updateSessionAfterMemberUpdatedEvent(MemberupdatedEvent event) {
       Member member = event.member();

       Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

       if (authentication != null && authentication.isAuthenticated()) {
           MyOAuth2Member principal = (MyOAuth2Member) authentication.getPrincipal();
           OAuth2AuthenticationToken oAuth2AuthenticationToken =
               new OAuth2AuthenticationToken(
                   principal.update(
                       member.getCoupleId(),
                       member.getNickname(),
                       member.getImageUrl()
                   ),
                   authentication.getAuthorities(),
                   principal.getNameAttributeKey()
               );

           SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken);

           SecurityContext securityContext = SecurityContextHolder.getContext();
           httpSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
       }
   }

ApplicationEventPublisher

  • 이벤트를 발행하는데 사용되는 인터페이스이다.

  • 이벤트는 어플리케이션의 상태 변화나 특정 작업이 발생했음을 나타내는 메시지이다.

    Spring에서 제공하는 package org.springframework.context; 인터페이스이기 때문에 생성자 주입이나 @Autowired 등을 통하여 쉽게 Bean을 주입 받을 수 있다.

    ApplicationEventPublisher의 특징

1. 이벤트 발행

  • 여러 Listner들 에게 특정 이벤트를 발생했음을 알린다.

2. 리스너 등록

  • 이벤트를 발행하면 이를 들을 Listener들은 미리 등록이 되어 있어야 한다.
    • @EventListener, @TransactionalEventListener를 활용하면 쉽게 리스너를 구현할 수 있다.

3. 동기식, 비동기식 처리

  • 기본적으로 이벤트는 동기식으로 처리되지만, 비동기식 처리를 위해 '@Async' 어노테이션을 활용할 수 있다.

동기, 비동기 공부 해야함.

이벤트 발행의 장점

1. 결합도 감소

  • 이벤트를 발행하는 곳에서는 이벤트를 수신하는 리스너에 대해서 알 필요가 없다.
  • 이벤트를 수신하는 리스너도 이벤트를 발행하는 발행자에 대해서 알 필요가 없다.
  • 서비스 간의 의존성을 줄인다.

2. 유지보수성 향상

  • 낮은 결합도는 시스템의 유지보수성을 향상시킨다. -> 어느 부분을 변경할 경우 영향을 미치는 부분이 적다는 의미이다.

3. 확장성

  • 새로운 이벤트 리스너를 추가함으로써 확장이 가능하다.

4. 비동기 처리

  • 이벤트 발행을 한 후 다른 작업을 할 수 있고 이벤트 리스너의 처리는 다른 쓰레드 혹은 프로세스에서 이루어질 수 있다. -> 성능 향상

5. 응집도 향상

  • 이벤트 리스너는 특정 이벤트에 대한 처리 로직만이 있다. 따라서 관련된 기능만을 관리할 수 있다.

여기서 든 나의 의문점) 이벤트 리스너는 여러개가 등록이 되어있을 것이다. 따라서 이벤트를 발행하면 어떤 이벤트 리스너가 발행된 이벤트를 듣고 처리할 수 있는가?

-> 이벤트 클래스 타입에 의존한다.

eventPublisher.publishEvent(new MemberupdatedEvent(findMember));

위 이벤트를 발행하는 코드의 클래스 타입은 MemberupdatedEvent 이다.

따라서

public void updateSessionAfterMemberUpdatedEvent(MemberupdatedEvent event)

가 이벤트를 듣고 처리할 수 있다.

이벤트 리스너 작동 방식

타입 기반 매칭

  • Spring은 발행된 이벤트 타입을 검사하고, 해당 타입의 이벤트를 처리할 수 있는 'EventListener'를 찾습니다.

멀티 리스너

  • 하나의 이벤트에 대해 여러개의 리스너가 존재할 수 있다.
  • 이벤트가 발행되면 여러개의 이벤트 리스너가 로직을 처리할 수 있다.

@EventListener vs @TransactionalEventListener

@EventListener

  • 이벤트가 발행되는 즉시 실행된다.

@TransactionalEventListener

  • 트랜잭션의 상태에 따라 이벤트를 처리한다.
  • phase속성을 통해 트랜잭션의 단계에 따라 실행한다.

phase 속성

  • AFTER_COMMIT

    • 트랜잭션이 성공적으로 커밋된 이후 이벤트 리스너 실행. 기본값이다.
  • AFTER_ROLLBACK

    • 트랜잭션이 롤백된 후 이벤트 리스너 실행
    • 트랜잭션이 실패할 경우 처리해야할 로직이 있을 경우 사용
  • AFTER_COMPLETION

    • 트랜잭션이 커밋 또는 롤백이 되어 완전히 종료된 후 이벤트 리스너를 실행한다.
    • 트랜잭션의 성공 여부와 관계없이 특정 작업을 수행하고 싶을 경우 사용
  • BEFORE_COMMIT

    • 트랜잭션이 커밋되기 직전에 이벤트 리스너가 실행된다.
profile
함께 있고 싶은 사람, 함께 일하고 싶은 개발자

0개의 댓글