현재의 레거시 프로젝트는 신규로 진행되는 프로젝트가 마무리 되면 사용을 중단할 예정이고, 현재 스크랩하는 웹 페이지가 1500개 정도 규모이고, 잦은 변동과 그에 따른 대응을 하기에는 각 스크랩 Class 를 Build 후 Admin 에 등록하는 환경을 유지해야 한다는 이유.
또한 하나의 프로젝트로 Build 되서 관리를 하려면, 기존 레거시 시스템에서 CI/CD 도입, 현재 Job 을 DB 에 등록해 주기적으로 Pooling 하는 방식에서 MQ 를 사용한 비동기 처리, 서버 스펙의 변동 등 대체될 프로젝트에 너무 많은 투자가 들어간다는 점을 근거로 제안은 거절되었다.
현재 데이터 수집을 담당하는 개발자는 7명이고, 한 사람이 하루에 진행하는 Build 수는 5건.
하루에 개발자 한 명이 소요하는 Build 시간이 3분이고, 팀 전체로는 21분, 5일 간 팀이 소요하는 Build 시간은 1시간 40분 정도.
팀 내에서 Build 에 필요한 요구사항을 조사했다.
1. 각 스크립트의 Class 이름으로 된 Main Class 단위로 Build 가 되야한다.
2. 현재의 라이브러리 버전을 유지하고, 사용이 가능해야 한다.
3. Build 결과물은 Jar File 이고, 각각의 Jar File 은 20~30MB 의 용량을 유지해야 한다.
첫 시작은 프로젝트 내에서 gradle init command 를 실행하는 것이다.
현재 프로젝트는 pom.xml 이 작성되어 있고, Gradle 을 미리 설치 해놓았기에 gradle init command 를 실행하는 것으로 쉽게 Gradle 을 시작할 수 있다.
두 번째는 라이브러리를 확인했다.
기존에 사용하던 모든 라이브러리의 종류와 version 을 확인하고, gradle dependency 에 추가했다.
세 번째로는 Build Script 를 작성했다.
초기의 Build 는 shadow plugin 을 활용한 fatJar 형태로 Build 를 진행했다.
fatJar 는 Build 후 Jar File 의 용량이 120MB 였고, 이는 요구사항에 맞지 않아 다른 방법을 찾는 것이 필요했다.
0. JVM ClassLoader
Java 로 작성 된 Source Code 가 실행되는 과정은 아래와 같다
java source(.java) -> Java Compiler -> Java Byte Code(.class) -> JVM(ClassLoader) -> Java interpreter + JIT compiler -> Native Code자세한 과정은 다루지 않고, JVM 위에서 실행되는 ClassLoader 에 맞춰보면 기본적으로 JVM 의 ClassLoader 는 Java Byte Code 즉, .class File Load 하기 위해 설계되었다.
기본적으로 Jar File 은 .class File 을 압축한 집합체이고, JVM ClassLoader 는 Jar File 을 실행 할 때 .class File 을 Load 한다.
1. Spring 환경
War Build & Excute
- War File 로 Build 하면 그 내부에 라이브러리 Jar File 을 포함하고 있다
- War File 은 기본적으로 WAS 위에서 동작한다. 서버의 JVM 위에서 Tomcat(WAS) 가 실행 (jvm system class loader 가 Tomcat 의 Main class 를 Load)
- Tomcat 내부의 ClassLoader (Jar File 을 해석하고 Load 가 가능) 가 War File 의 Main Class Load
- Main Class excute 이후 동적 ClassLoading 을 통해 라이브러리 Jar File 및 기타 class File Load
- War 내부의 Jar File 을 Load 할 수 있다
Jar Build & Excute
- 실행 가능한 fatJar 로 Build 시 모든 라이브러리를 class File 로 풀어낸 뒤 Jar 로 압축한다.
- JVM 의 classLoader 가 Jar File 내부의 Main Class Load
- Main Class excute 이후 동적 ClassLoading 을 통해 라이브러리 class File 및 기타 class File Load
- Spring 의 Jar File 은 JVM 의 기본 ClassLoader 를 사용하기 때문에 내부에 다른 Jar File 은 Load 할 수 없다
2. Spring Boot 환경
Jar Build & Excute
- Spring Boot 는 기본적으로 내장 Tomcat 을 가지고 있다
- Jar File 로 Build 하면 그 내부에 라이브러리 Jar File 을 포함하고 있다
- JVM 의 classLoader 가 Jar File 의 Main Class Load & Excute (JarLauncher)
- JarLauncher 가 실행되면서 LaunchedClassLoader 라는 새로운 ClassLoader 를 생성하고 등록
- LaunchedClassLoader extends JarUrlClassLoader extends URLClassLoader 의 관계
- 새로운 ClassLoader 가 등록되고 난 후 SpringBootApplication 실행
- 이후 동적 ClassLoading 을 통해 라이브러리 Jar File 및 기타 class File Load
- Jar File 내부에 다른 Jar File 을 Load 할 수 있다
용량 문제를 해결하기 위해 기존에 Build 된 Jar File 내부를 살펴봤다.
기존에 진행되던 Build Process 에서 용량 문제를 해결하기 위해 아래와 같이 Build 를 진행하고 있었다.
1. Build 되는 라이브러리는 각 version 에 맞는 Jar File 을 보관해 함께 Build
2. Jar 형태의 라이브러리를 Load 하고 사용 가능한 새로운 ClassLoader(Jar-in-Jar-Loader) 사용
새롭게 Build Script 를 작성하기 전에 위의 두 가지 사항에 대해 대응하기 위해 사전 작업을 진행했다.
1. 각 라이브러리 Jar File 을 project 의 src/main/resources/lib 경로에 저장
2. ClassLoader 가 라이브러리 Jar File 을 인식하고 그 내부의 .class 를 Load 할 수 있게 하는 작업
2번 작업을 진행하는 도중 새로운 사실을 알게 되었다. Jar File 을 인식하고 그 내부의 .class 를 Load 할 수 있는 URIClassLoader 가 이미 구현되어 있다는 것이였다.
기존에 Build Script 를 정의했던 사람의 경우에는 이 사실을 인지하지 못하고, Jar-in-Jar-Loader 라는 라이브러리를 함께 빌드하고 Jar File 실행 시 이 라이브러리의 main class 를 실행하도록 정의해두었다.
URIClassLoader 가 이미 정의 되어 있고 이는 Classpath 내에 존재하는 Jar file 을 인식할 수 있다는 점을 실제로 확인하기 위해 Build Script 를 새롭게 정의하고 실험을 진행했다.
build Script 를 새롭게 작성하고 진행한 build test 는 성공적이였다.
정상적으로 URIClassLoader 를 상속하는 ApplicationClassLoader 에서 jar 형태의 라이브러리를 로드하고 정상 실행이 가능했다.