Skip to content

Latest commit

 

History

History
114 lines (88 loc) · 9.46 KB

File metadata and controls

114 lines (88 loc) · 9.46 KB

01. 아키텍처와 패키지 구조

무엇을 / 왜

spring-jms는 한 가지 기술(Jakarta Messaging, 옛 JMS)을 감싸지만, 내부는 "보내는 쪽"과 "받는 쪽"이라는 두 축으로 갈린다. 이 장은 모듈 전체를 한눈에 조망하고, 모든 곳에서 공유되는 두 가지 토대 — **비검사 예외 변환 계층(JmsException)**과 공통 기반 클래스(JmsAccessor) — 를 먼저 짚는다. 이후 장들은 이 토대 위에 쌓인다.

JMS API의 두 가지 불편함이 이 토대의 존재 이유다. 첫째, 거의 모든 JMS 메서드가 검사 예외 jakarta.jms.JMSException을 던져 호출 코드가 try/catch에 강제로 묶인다. 둘째, ConnectionFactory에서 Connection/Session을 얻고 닫는 패턴이 어디서나 똑같이 반복된다. spring-jms는 전자를 런타임 예외 계층으로, 후자를 기반 클래스의 protected 메서드로 흡수한다.

핵심 타입

두 갈래 + 공통 토대

                    ┌─────────────────────────────────────────┐
                    │            공통 토대 (support)            │
                    │  JmsAccessor → JmsDestinationAccessor     │
                    │  JmsUtils(예외변환/자원닫기), JmsException  │
                    └───────────────┬─────────────────────────┘
                                    │
            ┌───────────────────────┴────────────────────────┐
            ▼ 보내는 쪽 (동기)                                  ▼ 받는 쪽 (비동기)
   ┌──────────────────────┐                        ┌──────────────────────────┐
   │ core.JmsTemplate      │                        │ listener.*Container       │
   │ core.JmsClient        │                        │ annotation.@JmsListener   │
   │ core.JmsMessagingTpl   │                        │ config.*Factory/Registry  │
   └──────────┬───────────┘                        │ listener.adapter.*        │
              │                                      └────────────┬─────────────┘
              ▼                                                   ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ connection: ConnectionFactory 어댑터, JmsTransactionManager,          │
   │             ConnectionFactoryUtils (트랜잭션 동기화된 Session)         │
   └────────────────────────────────────────────────────────────────────┘

양쪽 모두 결국 connection 패키지를 통해 실제 JMS Connection/Session을 얻고, support 패키지의 컨버터·리졸버로 페이로드와 목적지를 다룬다.

예외 변환 계층 — JmsException

루트 패키지(org.springframework.jms)에는 클래스 하나당 거의 한 파일씩 예외가 늘어서 있다. 이들은 전부 추상 클래스 JmsException(런타임 예외)을 상속한다. 핵심은 검사 예외를 비검사로 바꾸면서 의미를 보존하는 매핑이다.

RuntimeException
   └── JmsException (추상)
         ├── UncategorizedJmsException          분류 안 되는 JMSException
         ├── InvalidDestinationException         ← jakarta.jms.InvalidDestinationException
         ├── InvalidSelectorException            ← jakarta.jms.InvalidSelectorException
         ├── MessageFormatException, MessageNotReadableException, ...
         ├── JmsSecurityException, TransactionRolledBackException, ...
         └── (support 하위) MessageConversionException, DestinationResolutionException

이 매핑을 수행하는 정적 메서드가 JmsUtils.convertJmsAccessException(JMSException)이다. 들어온 JMSException의 구체 타입을 보고 대응하는 Spring 예외로 감싸 던지며, 매칭되는 것이 없으면 UncategorizedJmsException으로 떨어뜨린다. JmsAccessor.convertJmsAccessException(...)이 이 정적 메서드로 위임하므로, 모든 하위 클래스(JmsTemplate, 컨테이너)는 catch (JMSException ex) { throw convertJmsAccessException(ex); } 한 줄로 변환을 끝낸다.

공통 기반 — JmsAccessor / JmsDestinationAccessor

JMS에 접근하는 모든 헬퍼의 뿌리다. JmsTemplate도, 모든 리스너 컨테이너도 결국 이 둘을 상속한다.

JmsAccessor(support 패키지, InitializingBean 구현)가 담는 것:

  • connectionFactory — 모든 작업의 출발점. obtainConnectionFactory()로 null 검증과 함께 꺼낸다.
  • sessionTransacted, sessionAcknowledgeMode — Session을 만들 때의 트랜잭션/확인 모드.
  • protected 팩토리 메서드 createConnection(), createSession(Connection)ConnectionFactory.createConnection()Connection.createSession(transacted, ackMode)를 감싸 하위 클래스가 일관되게 자원을 만들게 한다.

JmsDestinationAccessor는 여기에 목적지 관련 상태를 더한다:

  • destinationResolver(기본 SimpleDestinationResolver), pubSubDomain(Queue냐 Topic이냐).
  • resolveDestinationName(session, name) — 이름 문자열을 Destination 객체로 해석.
  • receiveFromConsumer(consumer, timeout) — timeout 부호에 따라 receive(timeout)/receiveNoWait()/receive()를 갈라 호출. RECEIVE_TIMEOUT_NO_WAIT(-1), RECEIVE_TIMEOUT_INDEFINITE_WAIT(0) 상수가 여기 정의된다.
JmsAccessor                 (ConnectionFactory, 트랜잭션/확인 모드, 예외 변환)
   └── JmsDestinationAccessor  (+ DestinationResolver, pubSubDomain, receive 타임아웃)
          ├── JmsTemplate                          (동기 — ch.02)
          └── AbstractJmsListeningContainer → ...   (비동기 — ch.04)

동작 흐름 — 예외 변환이 일어나는 지점

JmsTemplate.receive()를 예로 들면, 변환 경계가 어디인지 한눈에 보인다.

JmsTemplate.receive()
   │
   ▼ execute(SessionCallback, startConnection=true)
   │   try {
   │       Session = ConnectionFactoryUtils.doGetTransactionalSession(...)  // 트랜잭션 동기화 확인
   │       ... action.doInJms(session) ...  ← 실제 JMS 호출 (JMSException 던질 수 있음)
   │   }
   │   catch (JMSException ex) {
   │       throw convertJmsAccessException(ex);   ← 여기서 비검사 JmsException으로 변환
   │   }
   │   finally {
   │       JmsUtils.closeSession(...); ConnectionFactoryUtils.releaseConnection(...);
   │   }
   ▼
호출자는 JmsException(런타임)만 본다 — try/catch 강제 없음

finally의 자원 정리 역시 JmsUtils.closeSession/closeMessageProducer/closeMessageConsumer로 통일되어 있다. 이들은 닫는 도중 발생한 예외를 삼키고 로깅만 하므로, 정상 흐름의 예외가 자원 정리 예외에 가려지지 않는다.

핵심 메서드

  • JmsUtils.convertJmsAccessException(JMSException): 모듈 전체의 예외 변환 단일 창구. JMS 표준 예외 타입을 Spring 예외로 1:1 매핑한다.
  • JmsAccessor.createSession(Connection): con.createSession(isSessionTransacted(), getSessionAcknowledgeMode()). 트랜잭션/확인 모드 설정이 실제 Session에 반영되는 단 한 곳.
  • JmsAccessor.isClientAcknowledge(Session): 확인 모드가 표준 3종(AUTO/CLIENT/DUPS_OK)도 TRANSACTED도 아닌 "벤더 특화 클라이언트 확인"인지 판별. 이후 컨테이너가 message.acknowledge()를 직접 부를지 결정하는 데 쓰인다.
  • JmsDestinationAccessor.receiveFromConsumer(consumer, timeout): 타임아웃 의미(블로킹/즉시/무한대기)를 한 메서드로 통일.

설계 포인트 / 확장점

  • 템플릿 메서드 + 일관된 자원 패턴: create*/close*가 전부 protected 메서드로 노출되어, 특수 JMS 드라이버 대응 시 하위 클래스가 자원 생성을 가로챌 수 있다.
  • 예외 변환의 중앙화: 변환 로직이 JmsUtils 한 곳에 모여 있어, 새 JMS 예외 타입 추가나 매핑 변경이 국소적이다. convertJmsAccessException을 오버라이드하면 애플리케이션별 예외 정책도 끼울 수 있다.
  • AOT/네이티브 힌트: connection.ConnectionFactoriesRuntimeHints가 프록시 기반(SingleConnectionFactory의 공유 Connection 프록시 등)에 필요한 리플렉션·프록시 힌트를 등록해 GraalVM 네이티브 이미지에서도 동작하게 한다.

정리

spring-jms는 "동기로 보내기"와 "비동기로 받기"라는 두 책임을, JmsAccessor/JmsDestinationAccessor라는 공통 기반과 JmsException 비검사 예외 계층 위에 세운다. 어디서 JMS를 만지든 자원 생성·정리는 기반 클래스의 protected 메서드로, 예외 변환은 JmsUtils 단일 창구로 수렴한다. 다음 장부터는 이 토대 위의 두 축을 차례로 파고든다 — 먼저 보내는 쪽인 JmsTemplate이다.