๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

AbstractAggregateRoot์™€ ApplicationEventPublisher ๋™์ž‘ ๊ณผ์ •

by dkswnkk 2025. 6. 28.

๊ฐœ์š”

์Šคํ”„๋ง์˜ ์ด๋ฒคํŠธ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ๋•Œ ํ”ํžˆ AbstractAggregateRoot ํ˜น์€ ApplicationEventPublisher๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ์ด๋ฒคํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋“ฑ๋ก๋˜๊ณ  ๋ฐœํ–‰๋˜์–ด ์ตœ์ข…์ ์œผ๋กœ ์ˆ˜์‹ ๋˜๋Š”์ง€ ์ „์ฒด์ ์ธ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด ๋ณธ๋‹ค.

 

AbstractAggregateRoot ๋™์ž‘ ๊ณผ์ •

AbstractAggregateRoot๋Š” Spring Data์—์„œ ์ œ๊ณตํ•˜๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋กœ, ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‹ค ์†์‰ฝ๊ฒŒ ๋“ฑ๋กํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค. ์ฃผ๋กœ DDD์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ ํ›„ ์™ธ๋ถ€์— ์˜๋ฏธ ์žˆ๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. 

์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ๋ฌธ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

@Entity
public class Order extends AbstractAggregateRoot<Order> {
    public void complete() {
        this.status = OrderStatus.COMPLETED;
        // ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋“ฑ๋ก
        registerEvent(new OrderCompletedEvent(this.getId(), /*...*/));
    }
}

์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ AbstractAggregateRoot.java๋ฅผ ์ƒ์†ํ•œ ์—”ํ‹ฐํ‹ฐ๋Š” registerEvent() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๋ฆฌ์ŠคํŠธ(domainEvents)์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. 

https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/domain/AbstractAggregateRoot.java#L48

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ๋“ฑ๋ก๋œ ์ด๋ฒคํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด Spring Data์˜ save(), saveAll(), delete(), deleteAll(), deleteAllInBatch(), deleteInBatch() ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ž๋™์œผ๋กœ ์ถ”์ถœ๋˜์–ด ๋ฐœํ–‰๋œ๋‹ค.(์ฐธ๊ณ ๋กœ deleteById()์˜ ๊ฒฝ์šฐ์—๋Š” ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด์œ ๋Š” #issues/2084๋ฅผ ํ™•์ธํ•˜์ž.)

@Transactional
public void completeOrder(Order order) {
    order.complete();          // ์ƒํƒœ ๋ณ€๊ฒฝ + registerEvent(...)
    orderRepository.save(order);   // save() ํ˜ธ์ถœ → ์ด๋ฒคํŠธ ๋ฐœํ–‰
}

@Transactional
public void cancelOrder(Order order) {
    order.cancel();            // cancel() ๋‚ด๋ถ€์—์„œ ์ด๋ฒคํŠธ ๋“ฑ๋ก
    orderRepository.delete(order); // delete() ํ˜ธ์ถœ → ์ด๋ฒคํŠธ ๋ฐœํ–‰
}

https://docs.spring.io/spring-data/jpa/reference/repositories/core-domain-events.html

์ด๊ฑด ๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์ธ๋ฐ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์•„์‰ฌ์šด ์ ์€ ๋„๋ฉ”์ธ ๋‚ด๋ถ€์—์„œ updateXxx()์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋”๋ผ๋„ save()๋‚˜ delete()๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉด ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด๋‹ค. JPA์˜ ๋”ํ‹ฐ ์ฒดํ‚น์„ ํ†ตํ•ด ์ƒํƒœ ๋ณ€๊ฒฝ์€ ๋ฐ˜์˜๋˜๋”๋ผ๋„, ์—…๋ฐ์ดํŠธ ํ–‰์œ„์— ๋ถˆํ•„์š”ํ•œ save()๋‚˜ delete() ํ˜ธ์ถœ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒจ ๊ตฌ์กฐ์ ์œผ๋กœ ๋‹ค์†Œ ์–ด์ƒ‰ํ•˜๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ๋‹ค.

์—ฌํŠผ ์ดํ›„ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๊ฐ€ ์‹ค์ œ๋กœ ๋ฐœํ–‰๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์—”ํ‹ฐํ‹ฐ์˜ ์ €์žฅ ๋˜๋Š” ์‚ญ์ œ ์‹œ์ ์— Spring Data ๋‚ด๋ถ€์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์ด๋ฅผ ApplicationEventPublisher๋ฅผ ํ†ตํ•ด ๋ฐœํ–‰ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ „์ฒด ํ๋ฆ„์„ ๋‹ด๋‹นํ•˜๋Š” ํ•ต์‹ฌ ํด๋ž˜์Šค๊ฐ€ ๋ฐ”๋กœ EventPublishingRepositoryProxyPostProcessor.java์ด๋‹ค.

https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java#L72

Spring Data๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๊ฐ์‹ธ๋Š”๋ฐ, ์ด ํ”„๋ก์‹œ ์ƒ์„ฑ ๊ณผ์ •์—์„œ EventPublishingRepositoryProxyPostProcessor๊ฐ€ ๊ฐœ์ž…ํ•˜์—ฌ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์œ„์˜ postProcess() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ, ์ด ์‹œ์ ์— EventPublishingMethodInterceptor๊ฐ€ ํ”„๋ก์‹œ์— ์ถ”๊ฐ€๋œ๋‹ค.

์ดํ›„ ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ repository.save(entity) ๋˜๋Š” repository.delete(entity)๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, ํ•ด๋‹น ์ธํ„ฐ์…‰ํ„ฐ์˜ invoke() ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java#L107

์ด invoke() ๋ฉ”์„œ๋“œ๋Š” ๋จผ์ € ์‹ค์ œ DB ์ €์žฅ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ ๋’ค, ๋Œ€์ƒ ์—”ํ‹ฐํ‹ฐ์— @DomainEvents ์• ๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ๋‚˜ AbstractAggregateRoot์˜ domainEvents() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ์ถ”์ถœํ•œ๋‹ค. ์ด๋ฒคํŠธ๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ํ•˜๋‚˜์”ฉ ApplicationEventPublisher.publishEvent()๋ฅผ ํ†ตํ•ด ๋ฐœํ–‰ํ•˜๊ณ , ๋งˆ์ง€๋ง‰์œผ๋กœ @AfterDomainEventPublication์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

๊ฒฐ๊ตญ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๊ฐ€ ์‹ค์ œ๋กœ ๋ฐœํ–‰๋˜๋Š” ์‹œ์ ์€ ์•„๋ž˜ EventPublishingMethod.publishEventsFrom() ๋ฉ”์„œ๋“œ์—์„œ ApplicationEventPublisher.publishEvent(...)๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋‹ค.

https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java#L195

์ด๋Ÿฌํ•œ ํ๋ฆ„์„ ํ†ตํ•ด ๋„๋ฉ”์ธ ๊ณ„์ธต์—์„œ๋Š” registerEvent()๋กœ ์ด๋ฒคํŠธ๋งŒ ๋“ฑ๋กํ•˜๋ฉด ๋˜๊ณ , save()๋‚˜ delete() ์‹œ์ ์— Spring Data๊ฐ€ ์ด๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœ·๋ฐœํ–‰ํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ๋œ๋‹ค.

์œ„ ๋‚ด์šฉ๋“ค์„ ํ•œ์ค„๋กœ ์š”์•ฝํ•˜๋ฉด AbstractAggregateRoot์—์„œ registerEvent()๋กœ ๋“ฑ๋ก๋œ ์ด๋ฒคํŠธ๋Š”, save() ๋˜๋Š” delete() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ ์งํ›„ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฆ‰์‹œ Spring Data JPA๊ฐ€ ApplicationEventPublisher๋ฅผ ํ†ตํ•ด ๋ฐœํ–‰๋œ๋‹ค.

 

ApplicationEventPublisher ๋™์ž‘ ๊ณผ์ •

ApplicationEventPublisher.publishEvent(event)๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ๊ฐ„๋ถ€ํ„ฐ, ์ด๋ฒคํŠธ๋Š” ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋”ฐ๋ผ ์ „ํŒŒ๋œ๋‹ค. ์ „์ฒด์ ์ธ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

โ‘ โ€ฏApplicationContext.publishEvent()

ApplicationContext๋Š” ApplicationEventPublisher๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ publishEvent(event)๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ปจํ…์ŠคํŠธ๊ฐ€ ๊ฐ€์žฅ ๋จผ์ € ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„๋“ค์ด๋Š” ์ง„์ž…์ ์ด ๋˜๋ฉฐ, ์‹ค์ œ ์ฒ˜๋ฆฌ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ AbstractApplicationContext.java์˜ publishEvent()๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค.

โ‘กโ€ฏAbstractApplicationContext.publishEvent()

์ „๋‹ฌ๋œ ๊ฐ์ฒด๊ฐ€ ApplicationEvent๋ฅผ ์ƒ์†ํ•˜์ง€ ์•Š์€ ์ˆœ์ˆ˜ POJO๋ผ๋ฉด, ์ด๋ฅผ PayloadApplicationEvent๋กœ ๋ž˜ํ•‘ ํ•œ๋‹ค. ๊ทธ ์ด์œ ๋Š” Spring 4.2 ์ด์ „์—๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ ApplicationEvent ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ด์•ผ ํ–ˆ์œผ๋‚˜ v4.2๋ถ€ํ„ฐ๋Š” ๋‹จ์ˆœํ•œ ๊ฐ์ฒด๋„ ์ด๋ฒคํŠธ๋กœ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. 

// Spring 4.2 ์ด์ „: ApplicationEvent ์ƒ์† ํ•„์š”
public class OrderCompletedEvent extends ApplicationEvent {
    private final Long orderId;
    public OrderCompletedEvent(Object source, Long orderId) {
        super(source);
        this.orderId = orderId;
    }
}

// 4.2 ์ดํ›„: ์•„๋ฌด POJO๋‚˜ ๋ฐ”๋กœ ๋ฐœํ–‰ ๊ฐ€๋Šฅ
record OrderCompleted(Long orderId) {}
publisher.publishEvent(new OrderCompleted(1L));

๋‹ค๋งŒ ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ์ „๋‹ฌ ์ฒด๊ณ„๋Š” ์—ฌ์ „ํžˆ ApplicationEvent ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, POJO๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ฉด์„œ๋„ ๊ธฐ์กด ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๋ ค๋ฉด ์ค‘๊ฐ„์— PayloadApplicationEvent๋ผ๋Š” ๋ž˜ํ•‘์ด ํ•„์š”ํ•˜๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ์Šคํ”„๋ง์€ ApplicationEvent๊ฐ€ ์•„๋‹Œ ์ผ๋ฐ˜ ๊ฐ์ฒด๊ฐ€ ์ „๋‹ฌ๋˜๋ฉด ์ด๋ฅผ PayloadApplicationEvent๋กœ ๊ฐ์‹ธ ๋‚ด๋ถ€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ํ๋ฆ„์— ๋งž์ถฐ ์ „ํŒŒํ•œ๋‹ค.

โ‘ข, โ‘ฃโ€ฏ โ€ฏSimpleApplicationEventMulticaster.multicastEvent()

์ด๋ฒคํŠธ๊ฐ€ ApplicationEventMulticaster์— ๋„๋‹ฌํ•˜๋ฉด, ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด์ธ SimpleApplicationEventMulticaster๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ , ํฌ๊ฒŒ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‘ ๊ฐ€์ง€ ๋™์ž‘์ด ์ผ์–ด๋‚œ๋‹ค.

https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java#L137

  1. ๋ฆฌ์Šค๋„ˆ ๋ชฉ๋ก ๊ฒฐ์ • – getApplicationListeners(event, type)๋กœ ํ˜„์žฌ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•  ๋ฆฌ์Šค๋„ˆ๋ฅผ ํ•„ํ„ฐ๋งํ•œ๋‹ค.
  2. ์‹คํ–‰ ๋ฐฉ์‹ ๊ฒฐ์ • – for-loop๋ฅผ ๋Œ๋ฉฐ ๊ฐ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋™๊ธฐ๋กœ ํ˜ธ์ถœํ• ์ง€ ์Šค๋ ˆ๋“œ ํ’€์—์„œ ๋น„๋™๊ธฐ๋กœ ํ˜ธ์ถœํ• ์ง€ ํŒ๋‹จํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋น„๋™๊ธฐ์˜ ์ „ํ™˜์€ ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ์กฐ๊ฑด์„ ๋งŒ์กฑํ–ˆ์„ ๋•Œ ์ด๋ฃจ์–ด์ง„๋‹ค.

์ฒซ ๋ฒˆ์งธ๋Š” TaskExecutor๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€์ด๋‹ค. ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™” ์‹œ ์ด๋ฆ„์ด applicationEventMulticaster ์ธ ๋นˆ์„ ์ง์ ‘ ๋“ฑ๋กํ•˜๊ณ  setTaskExecutor(...)๋กœ ์Šค๋ ˆ๋“œ ํ’€์„ ์ฃผ์ž…ํ•˜๋ฉด, ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ๋Š” ์Šค๋ ˆ๋“œ ํ’€์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ๋นˆ์„ ๋”ฐ๋กœ ์ •์˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ SimpleApplicationEventMulticaster๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๋ฐ, ์ด๋•Œ taskExecutor == null์ด๋ฏ€๋กœ ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ๋Š” ๋™๊ธฐ๋กœ ์‹คํ–‰๋œ๋‹ค.

@Configuration
public class EventConfig {

    // ์Šค๋ ˆ๋“œ ํ’€ ์ •์˜
    @Bean
    public ThreadPoolTaskExecutor eventPool() {
        ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
        pool.setCorePoolSize(4);
        pool.setMaxPoolSize(8);
        pool.initialize();
        return pool;
    }

    // ์ „์—ญ ์ด๋ฒคํŠธ ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ์— ์Šค๋ ˆ๋“œ ํ’€ ์ฃผ์ž…
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster multicaster(ThreadPoolTaskExecutor eventPool) {
        var m = new SimpleApplicationEventMulticaster();
        m.setTaskExecutor(eventPool); // ๋น„๋™๊ธฐ ์‹คํ–‰ ๊ฐ€๋Šฅ
        return m;
    }
}

์œ„์ฒ˜๋Ÿผ ์„ค์ •ํ•œ ๊ฒฝ์šฐ, ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ๋Š” TaskExecutor๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๋น„๋™๊ธฐ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•œ๋‹ค. ๋งŒ์•ฝ ์œ„์™€ ๊ฐ™์ด ๋ณ„๋„ ๋นˆ ๋“ฑ๋ก ์—†์ด ๊ธฐ๋ณธ ์„ค์ •์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” taskExecutor == null ์ƒํƒœ๋กœ ๋“ฑ๋ก๋˜์–ด ํ•ญ์ƒ ๋™๊ธฐ๋กœ ์‹คํ–‰๋œ๋‹ค.(๋‹ค๋งŒ ๋‚˜๋Š” ์ง€๊ธˆ๊นŒ์ง€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์œ„์™€ ๊ฐ™์ด applicationEventMulticaster๋ฅผ ์ง์ ‘ ์ •์˜ํ•œ ๊ฒฝ์šฐ๋Š” ๋ณด์ง€ ๋ชปํ–ˆ๋‹ค. ๋ณดํ†ต Listener ์ธก์— @Async๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ๋กœ ์ˆ˜ํ–‰ํ–ˆ๋‹ค.)

๋‘ ๋ฒˆ์งธ๋Š” ๊ฐ ๋ฆฌ์Šค๋„ˆ๊ฐ€ supportsAsyncExecution()์—์„œ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ์—ฌ๋ถ€์ด๋‹ค. ์ผ๋ฐ˜์ ์ธ @EventListener ๊ธฐ๋ฐ˜ ๋ฆฌ์Šค๋„ˆ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, @TransactionalEventListener์˜ ๊ฒฝ์šฐ์—๋Š” ํŠธ๋žœ์žญ์…˜ ๋ณด์žฅ์„ ์œ„ํ•ด ์‹ค์ œ๋กœ ๋‚ด๋ถ€ ๊ตฌํ˜„์—์„œ๋Š” supportsAsyncExecution()์ด false๋กœ ๊ณ ์ •๋˜์–ด false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋“ค์„ ์‚ดํŽด๋ณด์ž.

@Component
public class MailNotifier {

    @EventListener
    public void handle(OrderCompletedEvent e) {
        // ๋น„๋™๊ธฐ ์‹คํ–‰ ๊ฐ€๋Šฅ (์ „์—ญ ํ’€์ด ์žˆ์„ ๊ฒฝ์šฐ)
    }
}

๋งŒ์•ฝ applicationEventMulticaster ๋นˆ์— ์Šค๋ ˆ๋“œ ํ’€์„ ์ฃผ์ž…ํ•ด ์ฃผ์—ˆ๋‹ค๋ฉด ์œ„ ๋ฉ”์„œ๋“œ๋Š” ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰๋  ๊ฒƒ์ด๊ณ , ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ์—์„œ ๋™๊ธฐ๋กœ ์‹คํ–‰๋œ๋‹ค.

@EnableAsync
@Component
public class SmsNotifier {

    @Async
    @EventListener
    public void sendSms(OrderCompletedEvent e) {
        // ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ๋Š” ๋™๊ธฐ ํ˜ธ์ถœํ•˜์ง€๋งŒ, @Async ๋•๋ถ„์— ๋ฉ”์„œ๋“œ ๋ณธ๋ฌธ๋งŒ ๋ณ„๋„ ํ’€์—์„œ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendSms(OrderCompletedEvent e) {
        // supportsAsyncExecution() == false ์ด๋ฏ€๋กœ ํ˜ธ์ถœ ์ž์ฒด๋Š” ํ˜ธ์ถœ ์Šค๋ ˆ๋“œ
        // ๊ทธ๋Ÿฌ๋‚˜ @Async ๋กœ ์ธํ•ด ๋ณ„๋„ ํ’€์—์„œ ์‹คํ–‰
    }
    
}

๋ณดํ†ต์€ ์ด๋ ‡๊ฒŒ @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ ๋น„๋™๊ธฐ๋กœ ์ž‘์—…์„ ํ•  ๊ฒƒ์ด๋‹ค. ์ด ๊ฒฝ์šฐ ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ๋Š” ๋™๊ธฐ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€๋งŒ, @Async ๋•๋ถ„์— ๋ณธ๋ฌธ์€ ๋ณ„๋„ ์Šค๋ ˆ๋“œ ํ’€์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, ์ „์—ญ TaskExecutor ์„ค์ •๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ๋น„๋™๊ธฐํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

โ‘คโ€ฏSimpleApplicationEventMulticaster.invokeListener()

์ด ๋‹จ๊ณ„๋Š” ๋ฆฌ์Šค๋„ˆ ๋ฉ”์„œ๋“œ๋ฅผ ์ •๋ง๋กœ ํ˜ธ์ถœํ•˜๋Š” ๋งˆ์ง€๋ง‰ ์ง„์ž…์ ์ด๋‹ค. ์ด ๋ฉ”์„œ๋“œ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋™์ž‘์ด ์ˆ˜ํ–‰๋œ๋‹ค.

https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java#L162

  • ErrorHandler๊ฐ€ ์—†์Œ(๊ธฐ๋ณธ) : ๋™๊ธฐ ์‹คํ–‰์ผ ๊ฒฝ์šฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๋ฆฌ์Šค๋„ˆ์—์„œ ์ฆ‰์‹œ ์ƒ์œ„๋กœ ์ „ํŒŒ๋˜๊ณ , ์ดํ›„ ๋ฆฌ์Šค๋„ˆ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. ๋น„๋™๊ธฐ ์‹คํ–‰์ผ ๊ฒฝ์šฐ์—๋Š” ๊ฐ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ, ํ•œ ๋ฆฌ์Šค๋„ˆ์˜ ์˜ˆ์™ธ๊ฐ€ ๋‹ค๋ฅธ ๋ฆฌ์Šค๋„ˆ ์‹คํ–‰์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.
  • ErrorHandler๊ฐ€ ์žˆ์Œ : ์˜ˆ์™ธ๋ฅผ ํ•ธ๋“ค๋Ÿฌ๋กœ ์œ„์ž„ํ•˜์—ฌ ๋กœ๊น…ํ•˜๊ฑฐ๋‚˜ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‹ค๋ฅธ ๋ฆฌ์Šค๋„ˆ๋“ค์˜ ์‹คํ–‰์ด ๊ณ„์† ์ง„ํ–‰๋œ๋‹ค.

https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java#L178

์ดํ›„ doInvokeListener(listener, event)๋ฅผ ํ†ตํ•ด ์‹ค์ œ๋กœ ApplicationListener.onApplicationEvent()๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ๋Š” ๋ฆฌ์Šค๋„ˆ ํƒ€์ž…๊ณผ ์ด๋ฒคํŠธ ํƒ€์ž…์ด ์ผ์น˜ํ•˜์ง€ ์•Š์•„ ClassCastException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ,  ํƒ€์ž… ๋ถˆ์ผ์น˜์ผ ๊ฒฝ์šฐ TRACE ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ์กฐ์šฉํžˆ ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋˜์ ธ ํ˜ธ์ถœ์ž์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค.

๋˜ํ•œ ์ด๋ฒคํŠธ๊ฐ€ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋˜๋„๋ก ์„ค์ •๋˜์—ˆ๋”๋ผ๋„, ์Šค๋ ˆ๋“œ ํ’€์ด ๊ฐ€๋“ ์ฐจ๊ฑฐ๋‚˜ ์ข…๋ฃŒ๋˜์–ด RejectedExecutionException์ด ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ, ์Šคํ”„๋ง์€ ์ด ์˜ˆ์™ธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋˜์ง€์ง€ ์•Š๊ณ  ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™๊ธฐ์ ์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์šฐํšŒ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด๋กœ์จ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ ์‹œ์  ๋“ฑ์—์„œ๋„ ์ด๋ฒคํŠธ ์†์‹ค ์—†์ด ์•ˆ์ „ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

โ‘ฅโ€ฏ๋ฆฌ์Šค๋„ˆ ์ˆ˜ํ–‰

๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ๋Š” ์ตœ์ข…์ ์œผ๋กœ ์–ด๋Œ‘ํ„ฐ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น @EventListener ๋˜๋Š” @TransactionalEventListener ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ฐธ๊ณ ๋กœ @TransactionalEventListener ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” phase ์†์„ฑ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์˜ ์‹คํ–‰ ์‹œ์ ์„ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿผ ์—ฌ๊ธฐ์„œ "@TransactionalEventListener๋Š” ์–ด๋–ป๊ฒŒ ๋ฉ€ํ‹ฐ์บ์Šคํ„ฐ ๋‹จ๊ณ„์—์„œ ๋ฐ”๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  phase ์„ค์ •์— ๋”ฐ๋ผ ์‹คํ–‰ ์‹œ์ ์„ ๋ฏธ๋ฃฐ ์ˆ˜ ์žˆ์„๊นŒ?" ํ•˜๋Š” ๊ถ๊ธˆ์ฆ์ด ๋“ค ์ˆ˜ ์žˆ์„ํ…๋ฐ, ์ด ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ •๋‹ต์€ @TransactionalEventListener๋ฅผ ๊ฐ์‹ธ๋Š” ๋‚ด๋ถ€ ์–ด๋Œ‘ํ„ฐ ํด๋ž˜์Šค์ธ TransactionalApplicationListenerMethodAdapter.java์— ์žˆ๋‹ค.

SimpleApplicationEventMulticaster๋Š” ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ์— ๋Œ€ํ•ด ๋‹จ์ˆœํžˆ onApplicationEvent() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋ฟ์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ @TransactionalEventListener์˜ ๊ฒฝ์šฐ๋Š” ์ด ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์–ด์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋ฆฌ์Šค๋„ˆ ๋ฉ”์„œ๋“œ๋ฅผ ์ฆ‰์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ , ํŠธ๋žœ์žญ์…˜์— ํ›„์† ๋™์ž‘์œผ๋กœ ๋“ฑ๋ก๋งŒ ํ•ด๋‘๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค. ๋“ฑ๋ก ํ›„์— โ€ฏSimpleApplicationEventMulticaster์˜ ์—ญํ• ์€ ๋์ด๋‚œ๋‹ค.

https://github.com/spring-projects/spring-framework/blob/main/spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java#L85

๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๋ฉด ํ•ด๋‹น ๋ณธ๋ฌธ์˜ ์ฃผ์ œ๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒƒ ๊ฐ™์•„ ๊ฐ„๋žตํ•˜๊ฒŒ๋งŒ ์‚ดํŽด๋ณด๋ฉด AFTER_COMMIT๋กœ ๋“ฑ๋ก๋œ ๋ฆฌ์Šค๋„ˆ๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜๋”๋ผ๋„ ์ฆ‰์‹œ ์‹คํ–‰๋˜์ง€ ์•Š๊ณ , TransactionSynchronizationManager๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๋ฆฌ์Šค๋„ˆ๋ฅผ ํŠธ๋žœ์žญ์…˜ ํ›„์† ์ž‘์—…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝœ๋ฐฑ ๊ฐ์ฒด(TransactionalApplicationListenerSynchronization)๋กœ ๋“ฑ๋กํ•ด ๋‘”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๋Š” ์‹œ์ ์ด ๋˜๋ฉด PlatformTransactionManager๋Š” ๋“ฑ๋ก๋œ ์ฝœ๋ฐฑ๋“ค์˜ afterCommit() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ ,
๊ทธ ์•ˆ์—์„œ listener.processEvent(event) → invokeListenerMethod()๋ฅผ ํ†ตํ•ด ์ตœ์ข…์ ์œผ๋กœ ๋ฆฌ์Šค๋„ˆ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

๋Œ“๊ธ€