[Spring] Interface로 간단한 Listener 구현하기

 

✍️ 서론

이벤트의 콜백 메서드를 인터페이스의 추상 메서드로 정의하고, 이벤트에 관심 있는 클래스는 콜백 인터페이스를 구현함으로써 간단한 Listener를 만들 수 있다. 예제 코드로 이해해 보자.

 

여기 Member의 가입 로직이 포함된 MemberService가 있고,

@Service
public class MemberServiceImpl implements MemberService {

    @Autowired
    private MemberDao memberDao;

    @Override
    public void join(int memberId, String name) {
        Member exist = memberDao.get(memberId);
        if (exist != null)
            throw new RuntimeException("Exist id");

        memberDao.insert(new Member(memberId, name));
    }

}

 

모든 Member에게 알림을 전송하는 NotificationService가 있다.

@Service
public class NotificationServiceImpl implements NotificationService {

    @Override
    public void notify(int memberId, String name) {
    	// 모든 멤버에게 알림이라고 가정하자
        System.out.println("NEW MEMBER : " + memberId + " " + name);
    }
}

 

🍊 Interface 기반의 Listener

만약 MemberService의 join이 정상적으로 이루어졌을 때, 새로 가입한 멤버를 모든 멤버에게 알리고 싶다면 콜백 메서드를 활용할 수 있다. 앞서 언급했던 것처럼 콜백 메서드의 형태는 인터페이스의 추상 메서드로 정의할 수 있다.

 

아래는 join 이벤트의 콜백 메서드를 추상화한 인터페이스

public interface MemberJoinListener {

    void onMemberJoin(int memberId, String name);
}

 

NotificationService를 MemberJoinListener의 구현체로 만들어 콜백 메서드를 정의하고,

@Service
public class NotificationServiceImpl implements NotificationService, MemberJoinListener {

    @Override
    public void notify(int memberId, String name) {
        System.out.println("NEW MEMBER : " + memberId + " " + name);
    }

    @Override
    public void onMemberJoin(int memberId, String name) {
        notify(memberId, name);
    }
}

 

MemberService에선 MemberJoinListener의 구현체를 찾아서

@Autowired(required = false)
private List<MemberJoinListener> listeners;

 

아래와 같이 join의 마지막에 콜백 메서드를 호출하면 간단한 Listener를 구현할 수 있다.

@Service
public class MemberServiceImpl implements MemberService {

    @Autowired(required = false)
    private List<MemberJoinListener> listeners;

    @Autowired
    private MemberDao memberDao;

    @Override
    public void join(int memberId, String name) {
        Member exist = memberDao.get(memberId);
        if (exist != null)
            throw new RuntimeException("Exist id");

        memberDao.insert(new Member(memberId, name));

        // 리스너 호출
        if (listeners != null)
            listeners.forEach(l -> l.onMemberJoin(memberId, name));
    }

}

 

🤔 언제 사용해야 할까?

사실 위 예제는 리스너를 사용하지 않고 join에서 바로 notify를 호출해도 된다.

@Override
public void join(int memberId, String name) {
    Member exist = memberDao.get(memberId);
    if (exist != null)
        throw new RuntimeException("Exist id");

    memberDao.insert(new Member(memberId, name));

   notificationService.notify(memberId, name);
}

 

그럼 언제 사용해야 할까? 

만약 join이 완료됐을 때 여러 클래스에 콜백을 받길 원한다면 위 코드처럼 한 줄 한 줄 호출하는 것보다 리스너를 정의해서 리스트 기반으로 호출하는 것이 유지 보수에 유리하지 않을까 한다.