배포환경에서의 JAXB 에러 해결기

ljk·2024년 4월 25일

tldr;jaxb가 새 인스턴스를 생성 할 때 thread context class loader(TCCL)를 참조함에따라, class loader를 맞춰줘야 한다.

현재 진행 중인 프로젝트(spring boot 3, jdk 21)에서 http통신으로 받아온 xml을 parsing하는 기능이 있었다.

사실 해당 부분은 어렵지 않아서 jaxb를 dependency 추가 해주고 바로 완성하였다.

그런데 로컬에서 개발을 완료하고 개발서버에 배포해보니,


에러가 발생하였다.

3일간 고통의 삽질을 하다가 결국 알아낸것은

맨처음 webclient에서 요청을 받아온 후 bodyToMono를 통해서 xml parsing을 할 때,

Jaxb2XmlDecoder의 decodeToMono 메소드를 사용한다.


이 메소드는 내부 decode메소드를 또 호출 한 후,

unmarshal 메소드를 또 타고간 후

JaxbContextContainer의 createUnmarshaller을 호출한다.

createUnmarshaller 메소드는

타고타고 건너와서 결국 JAXBContext의 newInstance를 호출하고,

해당 메소드는 ContextFinder의 find를 호출하였다.

문제가 되었던 부분은 위 스크린 샷에서
ServiceLoaderUtil.firstByServiceLoader부분이였다.

load method에 break point를 잡고 debug모드로 확인해 보니, embed was(로컬에서 일반적으로 실행한 스프링 부트)에서는 동일한 class loader가 호출 되고있었으나 외장 was(로컬에서 tomcat10으로 구동)에서는 서로 다른 class loader(전달받은 service class는 AppClassLoader, TCCL은 parallelwebappclassloader)를 호출 하고 있었다.

그러다보니 생성된 ServiceLoader은 비어있어서 for문이 돌지 않고 null을 return하고 있었다.

문제는 해당 부분을 어떻게 override하느냐였는데,

해당부분이 bean도 아니다보니...
결국은 해당 library를 clone한 다음,

firstByServiceLoader메소드 내부에서

ServiceLoader.load(Class spiClass)
부분을
ServiceLoader.load(Class spiClass, spiClass.getClassLoader())
메소드로 수정하고

build gradle에서 추가하였다.

아쉬운점은...

해당 부분이 이미 올해 2월달에 pr이 올라왔었다.

https://github.com/jakartaee/jaxb-api/pull/296

괜히 엄한 구글링만하고 공식 repo를 제대로 안찾아봐서
시간을 너무 낭비하였다.

vue timepicker때도 그랬었지만,

다음부터는 꼭!! 공식 repo부터 확인해야겠다.

0개의 댓글