[Spring event] 스프링 비동기
Spring 비동기
회사에서 배포한 서버가 time-out이 났다.
회사 앱에 사용자 추가하면 AD 에 사용자도 추가해주는데 그 역할을 해주는 것이 ADAgent이다.
회사장비에 서비스를 올릴 때 **Server, ** Asset Agent, ** ADAgent 이렇게 3개의 앱을 올린다. 그 중에 ** ADAgent 가 저 윈도우 서버에 AD 에 사용자 추가해주는 역할을 하고 있는데 저 ** ADAgent 가 톰캣처럼 웹서버를 구동하고 있다.
그래서 회사앱 에서 사용자 추가하면, WAS 가 저 ** ADAgent 의 웹서버를 호출해서 AD 에 사용자를 추가해주고 있다.
그런데 저 ADAgent 의 웹서버를 WAS 가 호출했는데 응답이 없었던 것이다.
- AD(Active Directory) 라는 서비스이고 Windows Server 에서 제공해준다.
- SSO(Single Sign On) : 하나의 아이디로 어디서든지 로그인 가능하게 해주는 서비스
그래서 2가지를 수행해줘서 해결했다.
일단 DB에서 커넥션풀을 다 쓰고 있었다. 왜냐하면, restTemplate에서 timeout을 안걸어서 트랙잭션이 끝나질 않았다. 그래서 그 테이블에 락이 걸린거다.
oracle 은 트랜잭션이 시행된 지 일정시간이 지나면 알아서 커넥션을 끊어주는데 tibero는 그런 기능따위 없다.
회사에서 쓰는 스프링은 톰캣을 쓰는데 톰캣은 쓰레드 기반이다. 요청하나당 쓰레드 하나를 사용 > 트랜잭션을 하나 쓴다. commit이 안되는 트랜잭션이 계속 쌓인다.
그래서
-
ADAgent를 호출하고 응답을 받는 부분을 동기가 아닌 비동기로 바꾸면 되었다.
2. api를 동기에서 비동기로 전환
(1) timeout 설정
일단, 비동기로 바꿔도 계속 응답이 오는 걸 무작정 기다릴 수는 없다. 그러니 timeout을 설정하자
@Bean
public RestTemplate getRestTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(30_000); //30초
requestFactory.setReadTimeout(30_000);
return new RestTemplate(requestFactory);
}
- 위 코드에서
setConnectTimeout
은 서버와의 연결을 맺는데 소요되는 시간을 설정 -
setReadTimeout
은 서버로부터 응답을 받는 데 소요되는 시간을 설정 - 여기서 숫자에
_
를 중간에 삽입하여도 아무런 지장이 없다. 이는 숫자가 클 경우 몇 인지 가독성을 높이기 위해 붙이는데 쓰인다.
예를 들어
Long number = 123_456_789L;
이런 식으로 쓰인다.
(2) Spring Event 란?
Spring Framework에서 이벤트(Event)는 객체 간의 느슨한 결합(loose coupling)을 가능하게 하는 중요한 기능 중 하나이다. 이벤트는 객체가 다른 객체에게 상태 변화를 알리는 방법으로 사용된다.
Spring Framework에서 이벤트는 ApplicationEvent
클래스를 상속하는 클래스로 표현된다. ApplicationEvent
는 추상 클래스이며, 이벤트의 구체적인 유형은 이를 상속하는 클래스에서 정의된다.
Spring에서 이벤트를 발생시키려면, ApplicationEventPublisher
인터페이스를 구현한 클래스를 사용해야한다. Spring은 이 인터페이스를 구현한 ApplicationEventPublisherAware
인터페이스도 제공한다. 이 인터페이스를 구현하면, 객체 생성 시 Spring이 자동으로 ApplicationEventPublisher
를 주입해 준다.
이벤트를 처리하려면, ApplicationListener
인터페이스를 구현하는 클래스를 작성해야 한다. 이 인터페이스는 onApplicationEvent()
메서드를 정의하며, 이벤트가 발생했을 때 호출된다. 이벤트 핸들러를 등록하려면, ApplicationEventPublisher
를 사용하여 등록한다.
Spring에서는 이벤트의 처리 과정에서 다양한 기능을 제공한다. 예를 들어, 이벤트가 발생한 후 이벤트 처리가 완료될 때까지 기다리는 동기식 방식과 이벤트 발생 후 콜백을 받아 처리하는 비동기식 방식이 제공된다. 또한, 이벤트 처리 중 에러가 발생하면 해당 이벤트를 재전송하는 기능도 제공된다.
이벤트는 Spring에서 다양한 용도로 사용된다. 예를 들어, ContextRefreshedEvent
이벤트는 Spring 애플리케이션이 초기화되었을 때 발생하며, 이벤트 핸들러에서 초기화 작업을 수행할 수 있다. 또 다른 예로는, ApplicationEventMulticaster
클래스를 사용하여 다른 Spring 애플리케이션 간에 이벤트를 전달할 수도 있다.
(3) Spring event 사용하는 방법
총 3가지 단계가 있다.
Spring event 단계 : 이벤트 정의(ApplicationEvent
) > 이벤트 발행(ApplicationEventPublisher
) > 이벤트 처리(ApplicationListener
)**
ApplicationEvent
에서는 event를 생성해준다.ApplicationEventPublisher
는 이벤트 큐에 event를 넣어준다.ApplicationListener
는 이벤트 큐에서 하나씩 뽑아서 실행해준다.
여기서 Spring 4.0 이상부터 event 코드 방식이 바뀌었다.
일단은 바뀌기 전을 먼저 알려주겠다.
(3) - 1 Spring 4.0 이전
- 이벤트 정의
Spring에서 이벤트는 ApplicationEvent
클래스를 상속하는 클래스로 표현된다. 예를 들어, 다음은 MyCustomEvent
클래스를 정의하는 예제이다.
public class MyCustomEvent extends ApplicationEvent {
public MyCustomEvent(Object source) {
super(source);
}
// 이벤트 관련 메서드
public void doSomething() {
// 이벤트 처리 로직
}
}
- 이벤트 발행
이벤트를 발행하려면, ApplicationEventPublisher
인터페이스를 구현한 클래스를 사용한다. Spring은 이 인터페이스를 구현한ApplicationEventPublisherAware
인터페이스도 제공한다. 이 예제에서는 MyCustomEventPublisher
클래스가 이를 구현하도록 하겠다.
@Component
public class MyCustomEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void doSomethingAndPublishEvent() {
// 이벤트 발행
MyCustomEvent event = new MyCustomEvent(this);
eventPublisher.publishEvent(event);
}
}
위 예제에서 setApplicationEventPublisher()
메서드를 통해 ApplicationEventPublisher
를 주입받는다. doSomethingAndPublishEvent()
메서드를 호출하면, MyCustomEvent
객체를 생성하여 publishEvent()
메서드를 호출하여 이벤트를 발행한다.
이렇게 해도 되는데, 굳이 ApplicationEventPublisherAware
인터페이스를 구현할 필요가 없다. 왜냐하면, 이미 스프링 컨테이너가 구동될 때 ApplicationEventPublisher
가 bean으로 등록되기 때문에 추가 로직이 필요없다면 위 코드대로 하지 않고 (3) - 2 / 3번 처럼 하면 된다. 추가 로직이 필요하면 위 코드에 추가 로직을 넣어서 쓰면 된다.
- 이벤트 처리
이벤트를 처리하려면, ApplicationListener
인터페이스를 구현한 클래스를 작성한다. 이 예제에서는 MyCustomEventHandler
클래스가 이를 구현하도록 하겠다.
javaCopy code
@Component
public class MyCustomEventHandler implements ApplicationListener<MyCustomEvent> {
@Override
public void onApplicationEvent(MyCustomEvent event) {
// 이벤트 처리
event.doSomething();
}
}
위 예제에서 onApplicationEvent()
메서드를 구현하여 이벤트를 처리한다. 이벤트 핸들러를 등록하려면, @Component
어노테이션을 사용하여 Spring이 이 클래스를 자동으로 스캔하도록 한다.
- 이벤트 테스트
이벤트를 테스트하려면, MyCustomEventPublisher
클래스를 사용하여 이벤트를 발행하고, MyCustomEventHandler
클래스에서 이벤트 처리가 제대로 되는지 확인한다.
javaCopy code
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MyCustomEventPublisher.class, MyCustomEventHandler.class })
public class MyCustomEventTest {
@Autowired
private MyCustomEventPublisher publisher;
@Test
public void testCustomEvent() {
// 이벤트 발행
publisher.doSomethingAndPublishEvent();
}
}
(3) - 2 Spring 4.0 이상
- 이벤트 정의
@ToString
@Getter
public class MyCustomEvent {
private final MyJob myJob;
private final Map<String, Object> item;
@Builder
private MyCustomEvent(MyJob myJob, Map<String, Object> item) {
this.myJob = myJob;
this.item = defaultIfNull(item, Collections.emptyMap());
}
}
Spring 4.0 이 전에는 extends ApplicationEvent
이부분이 있었는데 상속을 받지 않는 것을 볼 수가 있다.
ApplicationEvent
를 상속받지않고 POJO 형식으로 이벤트를 정의할 수 있게 되었다. 그래서 굳이 ApplicationEvent
을 상속받지 않고 EventPublisher
를 통해서 발생시킬 수 있다.
- 이벤트 발행
이 부분은 없어도 된다. 왜냐하면 이벤트 처리에서 할 것이기 때문이다.
- 이벤트 처리
import com.commons.listener.myjob.event.MyCustomEvent;
import org.springframework.context.ApplicationEventPublisher;
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class CustomService {
private final ApplicationEventPublisher publisher;
@Transactional
public void custom(Map<String, Object> item) throws Exception {
publisher.publishEvent(
MyCustomEvent.builder()
.myJob(MyJob.ADD)
.item(item)
.build());
}
}
2번의 발행 부분의 publisher를 여기서 사용하게 된다.
훨씬 간단해진 것을 볼 수가 있다. 그리고 너무 Spring에만 의존하지 않게 된다.
Comments