티스토리 뷰

Spring Framework로 개발하다 보면 Service를 도메인 기준으로 나누는 경우가 많다.

그러다 보면 Aservice에서 BService로 참조를 하게 될 경우도 있고, 반대로 BService에서 AService로 참조할 수도 있게 된다.

이런 경우에 각 서비스 클래스를 빈으로 만들면서 서로를 참조하기 때문에 다음과 같은 오류를 만나게 된다.

서비스간 순환참조

 

실무에서는 매우 복잡한 비지니스 구조를 가지고 있기 때문에, 추후에 해당 문제를 해결하기 위한 방법이 애매하게 된다.

물론 서비스의 빈생성을 @Lazy 등.. 과 같은 방법으로 지연 로딩하여 생성 순서를 정해줄 수 있지만, 여러가 서비스가 얽혀 있는 상황이라면 깔끔한 방법이 되지 못한다.

 

이러한 구조에서 가장 간단하게 개발할 수 있는 방법으로 @EventListener을 선택했다.

@EventListner를 사용하면 두 서비스간의 의존성을 끊고, 이벤트 방식으로 필요한 처리를 각 서비스에 적절하게 위임할 수 있다.

 

@EventListner를 사용하는 방법은 다른 블로그에 자세하게 많이 나와있으니 참고하고, 간단하게만 말하자면

크게 Event 발생을 위한 Event객체와 Event발생 시 일어날 행위에 대한 EventHandler가 있고 이러한 이벤트를 publish 해줄 ApplicationEventPublisher로 이루어져 있다.

 

1. Event로 사용한 단순 Event클래스

@Data
public class AEvent {

    private Long id;

    public AEvent(Long id) {
        this.id = id;
    }
}

 

2. Event발생 시 일어날 행위에 대한 EventHandler

@Component
@RequiredArgsConstructor
public class AEventHandler {

    private final AService aService;

    @EventListener
    public void test(AEvent aEvent) {
        aService.eventTest(aEvent.getId());
    }

}

 

3. Event를 puslish 하는 ApplicationEventPublisher

@Service
@RequiredArgsConstructor
public class BService {

    private final ApplicationEventPublisher publisher;

    public void eventTest() {
        publisher.publishEvent(new AEvent(100L));
    }

}

ApplicationEventPublisher에서 event에 따라서 handler를 실행해주고 Handler에 등록된 이벤트 파라미터 객체에 따라 리스너로 동작하여 실행되게 된다.

서비스와 서비스 사이에서 ApplicationEventPublisher가 이벤트에 따라 연결해주기 때문에 두 서비스 간의 순환 참조가 해결된다.

 

* pulish한 결과의 리턴 값이 필요한 경우

만약 publish 후에 리턴값이 필요하다면 AEvent에 리턴 값을 받아 참조할 수 있다.

@Data
public class AEvent {

    private Long id;

    private String returnValue;

    public AEvent(Long id) {
        this.id = id;
    }

    public AEvent(Long id, String returnValue) {
        this.id = id;
        this.returnValue = returnValue;
    }
}
@Component
@RequiredArgsConstructor
public class AEventHandler {

    private final AService aService;

    @EventListener
    public void test(AEvent aEvent) {
        String returnValue = aService.eventTest(aEvent.getReturnValue());
        aEvent.setReturnValue(returnValue);
    }

}
@Service
@RequiredArgsConstructor
public class BService {

    private final ApplicationEventPublisher publisher;

    public void eventTest() {
        AEvent aEvent = new AEvent(100L, "Hi");
        publisher.publishEvent(aEvent);
        System.out.println("Request AEvent after returnValue : " + aEvent.getReturnValue());
    }

}

흐름을 요약해보면 BService -> publish -> AService -> AEvent returnValue 변경 -> BService에서 AEvent의 값 참조

 

@EventListener는 아래와 같이 부가적인 기능도 제공한다.

1. 기본적으로 동기 호출이지만 @EnableAsync, @Async로 비동기 호출도 가능

2. handler안에 같은 이벤트 객체를 받는 여러 파라미터가 있다면, @Order로 호출 순서를 지정

 

* ApplicationEventPublisher에서 어떻게 EventHandler의 @EventListener 함수를 호출할 수 있을까? 

ApplicationEventPublisher의 publishEvent를 찾아가다 보면 아래 메서드를 만난다.

AbstractApplicationContext

this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);

 

 

일반적으로 발생하는 ApplicationEvent인지(어플리케이션 생명주기에 따라 실행되는 이벤트)에 따라 구분되고, 위 함수로 실행되게 된다.

multicastEvent 메소드를 따라가 보면, 아래가 나온다.

SimpleApplicationEventMulticaster

 

결국 getApplicationListeners에서 이미 애플리케이션에 등록된 리스너를 찾아서 실행해 주고 있다.

getApplicationListeners는 SimpleApplicationEventMulticaster의 추상클래스인 AbstractApplicationEventMulticaster의 defaultRetriever에 정의된 리스너에 의해서 호출되고 있다.

public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    private final AbstractApplicationEventMulticaster.DefaultListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.DefaultListenerRetriever();
    final Map<AbstractApplicationEventMulticaster.ListenerCacheKey, AbstractApplicationEventMulticaster.CachedListenerRetriever> retrieverCache = new ConcurrentHashMap(64);
    ...
}

 

 

그럼 여기서 한가지 의문이 드는 건 언제 AbstractApplicationEventMulticaster에 리스너가 등록되냐는 것이다.

이건 에플리케이션이 실행하는 시점에 찾아볼 수 있다.

main메서드에서 실행되는 SpringApplication.run() 메서드를 찾아보면 아래와 같이 따라갈 수 있다.

@SpringBootApplication
public class BlogApplication {

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

}
public ConfigurableApplicationContext run(String... args) {
    ...
        context.setApplicationStartup(this.applicationStartup);
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
    ...
}
public void refresh() throws BeansException, IllegalStateException {
   ...
            this.initApplicationEventMulticaster();
            this.onRefresh();
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
   ...
}
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    ...

    beanFactory.setTempClassLoader((ClassLoader)null);
    beanFactory.freezeConfiguration();
    beanFactory.preInstantiateSingletons();
}

SpringApplication.run() -> this.refresh(context) -> this.refreshContext(context) -> this.finishBeanFactoryInitialization(beanFactory) -> beanFactory.preInstantiateSingletons()

위 경로를 따라오다보면 아래 메서드가 나온다.

 

DefaultListableBeanFactory.preInstantiateSingletons

org.springframework.context.event.internalEventListenerProcessor bean 이름을 가진 EventListenerMethodProcessor 클래스를 만나면서 싱글톤 인스턴트로 만들어지고 난 후의 작업이 실행된다.

(SmartInitializingSingleton을 상속하고 있는 클래스가 몇개 있는데, 해당 클래스를 상속하면 싱글톤 초기화 시 작업이 이루어지는 클래스가 몇 개 있는 것 같다.)

결국 해당 메소드를 따라가다 보면 AbstractApplicationContext 추상 클래스 변수 ApplicationEventMulticaster에 추가되고 있다.

 

 

정리하자면 

SimpleApplicationEventMulticaster -> AbstractApplicationEventMulticaster -> ApplicationEventMulticaster에서

애플리케이션 시작 시점에서 DefaultListableBeanFactory가 AbstractApplicationContext의 AbstractApplicationEventMulticaster로 리스너 핸들러를 등록해주고, AbstractApplicationContext 상위 인터페이스의 ApplicationContext가 상속한 ApplicationEventPublisher를 통해서 publish 할 수 있게 된다.

 

(내용이 맞는지 모르겠지만, 남이 짠 코드 해석도 힘든데 저걸 짠 사람은 얼마나 대단할까..-_-)

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함