Skip to content

spring EventListener #

Find similar titles

2회 업데이트 됨.

Edit
  • 최초 작성자
  • 최근 업데이트

Structured data

Category
Programming

spring EventListener #

  • jQuery에서 click, change 등의 특정 이벤트가 발생 시 콜백함수를 재정의하듯 spring에서도 특정 이벤트 발생 시 처리할 로직을 EventListener를 통해 구현할 수 있다.
  • spring 4.2버전 전후로 구현 방법에 차이가 있다.

4.2 이전 #

  • 4.2 이전에서는 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 각각 상속받아 구현한다.
  • 이벤트를 발생시킬 객체(대상)는 ApplicationEvent를 상속받고 해당 이벤트를 받아 처리할 구현 클래스에 ApplicationListener를 상속받아 구현한다.
  • ApplicationListener의 제네릭 타입은 발생시킬 대상 객체를 넣어준다.

이벤트 대상 객체 #

@Data
public class CustomApplicationEvent extends ApplicationEvent{

    private String message;


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

}

이벤트 리스너 #

public class CustomEventListener implements ApplicationListener<CustomApplicationEvent>{

    @Override
    public void onApplicationEvent(CustomApplicationEvent event) {
        String message = event.getMessage();
        System.out.println(message);
    }

}

4.2 이후 #

  • 4.2 이후에서는 어노테이션을 활용하여 spring의 핵심 키워드인 POJO형태의 클래스를 만들어 구현한다.
  • 이벤트 발생 대상 객체는 DTO및 VO 형태의 클래스로 만든다.
  • 이벤트 구현 클래스는 빈 등록 어노테이션(@Component)만 추가하고 POJO형태로 만들고 메서드를 만들어 해당 메서드에 @EventListener 어노테이션을 추가한 후 이벤트 대상 클래스를 정의한 후 구현해주면 된다.
  • 4.2 이전의 이벤트 클래스를 상속받는 방법은 하나의 대상에 하나의 객체를 만들어 줘야 하는 불편함이 있고 응집도가 낮아 비효율적이지만 어노테이션을 활용하면 하나의 이벤트 구현 클래스를 POJO형태로 만들어 줌으로써 응집도가 높아져 비교적 효율적으로 구현할 수 있다.
  • 아래 예시는 이벤트 리스너와 AOP를 활용한 push 알림 로직이다.

AOP(Advice) #

@Aspect
public class PushAdvice {

    @Autowired
    private WebApplicationContext context;

    // 이벤트를 발생시킬 메서드의 정보 - key : 클래스QN + 메서드명, value : message
    private Map<String, String> adminPushMap = new HashMap<>();
    // 실행된 클래스의 정보
    private Map<String,Object> classInfo = new HashMap<>();

    @Pointcut("execution(* com.insilicogen.common..service.*.*(..))")
    public void pushPointCut() {}

    @PostConstruct
    public void init() {
        adminPushMap.put("com.insilicogen.common.board.service.BoardService.insertBoard", "'%s'님이 공지사항을 등록했습니다");
    }

    @Around("pushPointCut()")
    public Object aroundPushAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 실행되는 클래스 정보
        Object target = joinPoint.getTarget();
        // 실행되는 메서드 정보
        Signature signature = joinPoint.getSignature();

        // 메서드의 return value
        Object resultVal = joinPoint.proceed();

        //--------------------- 위빙포인트(현재를 기준으로 메서드 실행 전,후로 나뉨)-----------------------

        // 실행되는 메서드의 파라미터
        Object[] args = joinPoint.getArgs();

        // 클래스 QN + 메서드명(이벤트 발생시킬 메서드를 담은Map의 Key)
        String targetName = String.format("%s.%s", target.getClass().getName(), signature.getName());

        for(String key : adminPushMap.keySet()) {
            if(targetName.equals(key)) {
                classInfo.put("target", target);
                classInfo.put("signature", signature);
                classInfo.put("resultVal", resultVal);
                context.publishEvent(new AdminPushCustomEvent(adminPushMap.get(key), args, classInfo));
                break;
            }
        }

        return resultVal;
    }
}

이벤트 발생 대상 객체 #

@Data
public class AdminPushCustomEvent{

    public AdminPushCustomEvent(String pushMsg, Object[] args, Map<String, Object> classInfo) {
        this.pushMsg = pushMsg;
        this.args = args;
        this.classInfo = classInfo;
    }

    private String pushMsg;
    private Object[] args;
    private Map<String, Object> classInfo;

}

이벤트 구현 #

@Component
@RequiredArgsConstructor
public class CustomEventListener {

    @Autowired
    private SimpMessagingTemplate messageTemplate;

    @Resource(name = "pushService")
    private PushService pushService;


    // 관리자에게 푸쉬알림 전송
    @EventListener(AdminPushCustomEvent.class)
    public void adminPushEventHandler(AdminPushCustomEvent event) {

        String pushMsgFormat = event.getPushMsg();
        Map<String, Object> classInfo = event.getClassInfo();
        Object[] args = event.getArgs();

        Object resultVal = classInfo.get("resultVal");

        String pushMsg = null;

        // 등록자 아이디 찾아내어 메세지 셋팅
        if((int)resultVal > 0) {
            for(Object arg : args) {
                if(arg instanceof CommonVO) {
                    CommonVO common = (CommonVO) arg;
                    pushMsg = String.format(pushMsgFormat, common.getRgtrId());
                }
            }
        }else {
            return;
        }

        List<String> adminIdList = pushService.selectUserId(2);
        int result = 0;

        for(String userId : adminIdList) {
            PushVO pushVO = new PushVO();
            pushVO.setUserId(userId);
            pushVO.setPushMsg(pushMsg);
            result = pushService.insertPush(pushVO);
        }

        if(result > 0) {
            for(String userId : adminIdList) {
                messageTemplate.convertAndSend("/topic/common/push/" + userId,pushMsg);
            }
        }
    }

}

이벤트 발생 방법 #

  • 위 AOP 구현 로직에서 알 수 있듯이 WebApplicationContext(spring의 최상위 객체) 의 publishEvent 메서드를 사용하여 이벤트를 발생시킬 수 있다.
  • WebApplicationContext는 ApplicationContext를 상속받고 있고, ApplicationContext는 다시 ApplicationEventPublisher를 상속받는 구조다.
  • ApplicationEventListener는 하나의 스레드가 이벤트 리스너에 정의한 동작을 수행하고, 해당 동작이 끝난 후 남은 작업을 수행하는 동기 방식으로 동작한다.

spring에서 제공하는 기본 EventListener #

  • spring에서는 서버가 처음 로드되는 시점, 세션의 생성과 삭제 등의 서버 안에서 일어나는 이벤트 등이 발생했을 때 구현할 수 있도록 재정의되어있다.
클래스명 내용
ContextRefreshedEvent ApplicationContext를 초기화하거나 새로 고칠 때 발생
ContextStartedEvent ConfigurableApplicationContext에서 start() 메서드를 호출하여 이 이벤트를 트리거하고 ApplicationContext를 시작
ContextStoppedEvent ConfigurableApplicationContext에서 stop() 메서드를 호출하여 ApplicationContext가 중지될 때 발생
ContextClosedEvent ConfigurableApplicationContext의 close() 메서드를 사용하여 ApplicationContext가 닫힐 때 발생
HttpSessionListener 세션이 생성되너가 소멸되었을때 이벤트 발생
0.0.1_20230725_7_v68