이벤트 기반 개발 : 시스템이 이벤트를 발생시키고, 이를 구독(Listener) 하는 컴포넌트가 반응하는 방식
비유하자면 카페에서 주문(이벤트)을 하면 바리스타가 커피를 만드는 것처럼, 작업이 분리되어 처리하는 방식이다.
장점
- 느슨한 결합 : 이벤트 발행자와 소비자가 독립적
- 확정성 : 비동기 처리로 부하 분산
- 반응성 : 실시간 처리 가능
- 예시 : 계좌 이체 후 이메일 알림, 결제 완료 후 재고 감소 등
Spring에서의 이벤트 처리
도구
- ApplicationEvent : 이벤트 정의
- AppliactionEventPublisher : 이벤트 발행
- @EventListener : 이벤트 수신
- 비동기 : @Async로 이벤트 처리 비동기화
Executor
- Java 에서 비동기 작업을 실행하기 위한 인터페이스
- 단일 메서드 execute(Runnable)을 정의하며, 작업을 스레드에 위임
비유하면 Executor는 공장 관리자이며, 작업자(스레드)를 직접 관리하지 않고, 작업표(Runnable)을 주면 알아서 처리하라고 맡기는 것.
- 구현체
ㄴ ThreadPoolTaskExecutor : 스레드 풀 기반, 커스텀 가능 > 해당 예제에서 사용
ㄴ ScheduledExecutorService : 주기적 작업 (예시 : 매일 환율 갱신)
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(2);
--
비동기 설정 클래스 ( main 에다가 @EnableAsync 를 붙여 사용해도 되지만, 조금 더 효율적으로 사용이 가능 )
@Configuration
@EnableAsync // 비동기 기능 활성화 (@Async 사용 가능)
public class AsyncConfig {
@Bean(name = "taskExecutor") // 비동기 작업을 위한 커스텀 Executor 빈 등록
public Executor taskExecutor() {
// ThreadPoolTaskExecutor: 스레드 풀 기반 비동기 실행기
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 기본 스레드 수: 5개 (평소 유지되는 스레드)
executor.setCorePoolSize(5);
// 최대 스레드 수: 10개 (큐가 가득 차면 증가)
executor.setMaxPoolSize(10);
// 대기 큐 크기: 100개 (초과 요청 대기열)
executor.setQueueCapacity(100);
// 스레드 이름 접두사: 로그에서 스레드 구분 용이
executor.setThreadNamePrefix("AsyncThread-");
// 작업 거부 정책: 큐와 풀이 가득 찼을 때
executor.setRejectedExecutionHandler((r, e) -> {
System.err.println("작업 거부: " + r + ", 풀 상태: " + e.getActiveCount());
});
// 초기화: 설정 적용 및 실행 준비
executor.initialize();
return executor; // Spring 컨텍스트에 등록
}
}
TransferEvent
@Getter
public class TransferEvent extends ApplicationEvent {
private final String fromAccount;
private final String toAccount;
private final double amount;
public TransferEvent(Object source,
String fromAccount,
String toAccount,
double amount) {
super(source);
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
}
TransferService
@Service
public class TransferService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void transfer(String fromAccount, String toAccount, double amount) {
System.out.println("이체 시작: " + fromAccount + " -> " + toAccount + ", 금액: " + amount);
eventPublisher.publishEvent(new TransferEvent(this, fromAccount, toAccount, amount));
System.out.println("이체 완료");
}
}
NotificationListener
@Slf4j
@Component
public class NotificationListener {
// 이벤트 리스너: 이체 이벤트 처리
@EventListener // TransferEvent 발생 시 호출
@Async // 비동기 실행 (AsyncConfig의 taskExecutor 사용)
public void handleTransferEvent(TransferEvent event) {
try {
Thread.sleep(2000); // 비동기 동작 확인용 지연
log.info(
"알림 전송: {}가 {}에게 {}원 보냄 (스레드: {})",
event.getFromAccount(),
event.getToAccount(),
event.getAmount(),
Thread.currentThread().getName()
);
} catch (InterruptedException e) {
log.error("알림 처리 중 오류: {}", e.getMessage(), e); // 예외 로깅
Thread.currentThread().interrupt(); // 스레드 상태 복구
}
}
}
TransferController
@RestController
@RequiredArgsConstructor
public class TransferController {
private final TransferService transferService;
@PostMapping("/transfer")
public String performTransfer(@RequestParam String from,
@RequestParam String to,
@RequestParam double amount) {
transferService.transfer(from, to, amount);
return "이체 요청 완료";
}
}
연속으로 실행했을 경우 결과값
'Spring > Spring-detail' 카테고리의 다른 글
동시성 문제 해결 - Synchronized, Pessimistic Lock, Optimistic Lock, Redis (0) | 2024.04.08 |
---|---|
@Transactional 안에 @Transactional 테스트 (0) | 2024.03.04 |
@Conditional 을 이용해 특정 조건일 때만 사용 (0) | 2024.02.22 |
@ConfigurationProperties / @ConfigurationPropertiesBean (0) | 2023.08.23 |
JAVA 싱글톤, Spring 싱글톤에 대해. (0) | 2023.03.23 |
댓글