지난번 SpringBoot에서 SpringBatch를 동작시키는데, External Class들을 잡는데 생기는 문제점들이 있어서
지난번 글에 MANIFEST.MF에 org.springframework.boot.loader.JarLauncher 가 Main-Class 에 적혀 있고
실제 개발한 코드의 메인은 Main-Class에 적힌 JarLauncher가 호출해준다는 부분을 언급했습니다.
JarLauncher 코드를 보며 클래스로더를 봤을때는 "클래스로더로 커스텀으로 구성하던지 해야겠구나" 로 생각했는데
검색해보니 역시나 해결방법은 있었습니다.
org.springframework.boot.loader.JarLauncher 말고도 빌드된 jar파일을 보면 다른 클래스들이 함께 들어있는데
다른 여러 클래스들이 있지만 Launcher 구현체는 WarLauncher와 PropertiesLauncher 두가지가 추가로 있습니다.
org.springframework.boot.loader.PropertiesLauncher 소스를 열어보니 처음 시도하던 -cp로 잡은 클래스패스와 BOOT-INF를 같은 클래스로더로 구성하는 방식은 아니지만 클래스패스 경로를 지원하는 설정을 통해 BOOT-INF와 별도로 설정한 클래스 패스 경로를 설정할 수 있도록 작성되어있습니다
그설정은 바로 "loader.path" 입니다.
저는 실행시 -Dload.path=userLib/ 형식으로 주거나 별도 설정 파일로 영역을 열어줄 예정입니다.
"loader.path"의 특이사항으로는 해당 영역의 클래스와 jar 그리고 리소스를 모두 클래스 패스에 한번에 잡아준다는 부분이며 해당 클래스패스에 있는 Configuration 클래스들도 Bean으로 잘 등록되는것을 확인했습니다.
물론 이 부분이 해결됬다고 하더라도 추가적으로 해야할 부분은 많이 남았습니다.
외부 클래스들을 클래스 패스로 제공하면 현재는 패키지가 동일한 클래스를 테스트 했지만
다른 패키지라면 componentScan을 어떻게 제공할 것인지
xml 설정은 어떻게 커버할 것인지 등.. 하나씩 시도해보고
막히는 부분이나 기록할 부분이 있으면 기록을 남길 예정입니다.
(작성하다보니 이건 SpringBatch 라기보다는 SpringBoot 활용 관련 이슈군요..)
아 그렇군 그럼 "java -cp boot.jar:app.jar com.meteor.AppMain" 가 아니라
"java -cp boot.jar:app.jar org.springframework.boot.loader.JarLauncher" 를 부르면 되겠군!
이라고 생각했지만 AppMain은 불리지만 문제가 있었습니다.
app.jar에 포함되어있는 내용은 job의 선언부 즉, configuration인데 이 클래스에 Bean들을 등록하다가
org.springframework.batch.core.Step 클래스를 못찾는 문제가 발생했습니다
"응? org.springframework.batch.core.Step은 그냥 boot.jar안에 lib에 Spring-batch쪽에 있는..건데..? 왜 못찾지?"
사실 위에 힌트가 있습니다
클래스 못찾는 문제니 당연히 클래스 로더 이슈(클래스로더가 잘못됬다는건 아닙니다, 대부분 사람이 문제죠) 였습니다.
현재 Configuration클래스는 위에 -cp로 잡았기 때문에 시스템 클래스로더에 물려있습니다.
그리고 org.springframework.batch.core.Step는 아까 JarLauncher가 만든 LaunchedURLClassLoader에 물려있습니다.
그리고 서로의 관계는 LaunchedURLClassLoader의 부모로 시스템 클래스 로더로 구성되어있죠
일반적인 클래스로더 구조 보통 상위 클래스 로더에 있는지 찾고 내려오는 구조입니다.
(일반적으로 상위 클래스 로더는 child를 알진 못합니다.)
즉 Thread에 ContextClassLoader는 LaunchedURLClassLoader로 설정 했으니
LaunchedURLClassLoader를 통해 Step이든 ConfigurationClass든 다 볼수 있는건 맞지만
Step만 로딩 한다면, 시스템 클래스로더에는 없으니 내려와서 LaunchedURLClassLoader 에서 찾고
ConfigurationClass를 로드 할때는 시스템 클래스로더에 있으니 바로 로딩했을겁니다.
그런데 ConfigurationClass를 막상 돌려보니 안에 Step.class가 있고 ConfigurationClass를 로딩한 시스템 클래스로더로 Step.class를 로드하니 없어서 에러가 난것으로 보입니다.
쉽게 말하면 parent클래스 로더에서 parent클래스 로더에 있는 클래스(child의 클래스를 바라보는)가 하위를 바라보니 시스템 클래스로더는 없는 클래스니 에러난거죠, JobConfig에서 Step을 참조할때 LaunchedURLClassLoader 를 쓰면 되겠지만, 에러가나는 코드는 빈등록하다 에러난거라..