Devin.KR

빌드와 실행 산출물

90분 안팎

학습 목표

Maven 캐시·버전 고정·외부 설정으로 실행을 재현합니다.

개념

테스트 통과와 배포물 완성을 구분합니다

동아리 API의 모든 테스트가 통과해도 배포 파일에 의존성이 빠지면 Java 실행은 실패합니다. 개발 IDE는 많은 jar를 클래스패스에 올려 주지만 서버의 java -jar 명령은 전달된 산출물의 규약을 따릅니다. 이번 레슨은 같은 소스를 다시 빌드하는 요령보다 검증한 파일을 명확히 식별하고 다른 환경에 그대로 전달하는 데 집중합니다. 앞 모듈의 대여·권한 코드를 보존한 프로젝트에서 실행 가능한 Spring Boot jar를 만들고 그 안의 진입점과 의존성 위치를 확인합니다.

POM과 Wrapper의 담당을 나눕니다

pom.xml의 부모 버전은 Spring Boot 3.1.5이고 java.version은 17입니다. 의존성 좌표와 빌드 플러그인은 이 프로젝트가 무엇으로 만들어지는지를 표현합니다. mvnw와 .mvn/wrapper 파일은 사용할 Maven 배포 버전을 정합니다. Wrapper가 JDK까지 설치하거나 모든 의존성을 내장하는 것은 아닙니다. Java 실행 버전과 Maven 캐시를 별도로 준비해야 합니다. 기존 미션을 복사할 때 숨김 폴더 .mvn을 빠뜨리면 시스템 Maven에 의존하거나 래퍼 실행부터 실패할 수 있습니다.

오프라인 실패는 준비 상태부터 읽습니다

./mvnw -o -q test의 -o는 Maven이 온라인 저장소에 접근하지 않게 합니다. 필요한 라이브러리와 플러그인이 캐시에 없다면 Cannot access central in offline mode 또는 artifact has not been downloaded 같은 진단을 만날 수 있습니다. 이는 업무 테스트의 assertion 실패와 구분합니다. 또한 Wrapper가 사용할 Maven 배포 파일도 먼저 있어야 합니다. 지도자에게 누락 좌표를 전달하여 준비하고 다시 실행합니다. 캐시 확보 없이 테스트를 건너뛰거나 버전을 임의로 바꾸는 행동은 실행 재현성을 해결하지 않습니다.

package까지의 단계가 무엇을 실행하는지 봅니다

Maven package는 앞 단계인 컴파일과 테스트를 포함합니다. ./mvnw test는 테스트 결과만 확인하므로 jar 생성까지 완료했다는 뜻이 아닙니다. bash package.sh는 오프라인 package 후 report.py와 artifact-check.py를 순서대로 실행합니다. -q는 정보 로그를 줄일 뿐 테스트가 출력하는 모든 문장을 없애지는 않습니다. 보고서의 Tests, Failures, Errors, Skipped를 함께 확인합니다. 앞 단계 156개와 추가 정책 4개가 모두 실행되어도 실제 jar 검사는 별도 단계라는 점을 유지합니다.

여러 main 중 API 진입점을 지정합니다

이 프로젝트에는 과거 CLI 레슨의 main 메서드도 남아 있습니다. 빌드 도구가 자동으로 하나를 찾게 맡기면 여러 후보가 있어 패키징이 모호해질 수 있습니다. spring-boot-maven-plugin에 mainClass를 lab.ApiApplication으로 지정합니다. 최종 jar 이름은 club-api로 고정하여 Dockerfile이나 실행 안내가 긴 버전 문자열에 의존하지 않게 합니다. 파일 이름이 고정되어도 내용은 릴리스마다 달라질 수 있으므로 버전 기록과 해시는 별도로 보존해야 합니다.

Boot 실행 jar의 구조를 직접 검사합니다

Boot 3.1.5 실행 jar의 manifest에는 Main-Class로 전용 JarLauncher, Start-Class로 lab.ApiApplication이 들어갑니다. 애플리케이션 클래스는 BOOT-INF/classes에, 라이브러리는 BOOT-INF/lib에 담깁니다. 일반 jar와 실행 jar는 확장자가 같으므로 파일명만 보고 판단하지 않습니다. artifact-check.py는 ZIP을 열어 두 진입점과 중첩 라이브러리 존재를 확인합니다. ArtifactPolicy의 단위 검사는 이 판단 규칙을 연습하는 것이며 실제 생성 파일을 열어 본 검사를 대신하지 않습니다.

배포할 파일과 참고 파일을 분리합니다

Boot repackage는 원래 일반 jar를 .original로 보관할 수 있습니다. 실행 계약을 확인한 target/club-api.jar를 전달합니다. target 전체나 이름에 jar가 있는 첫 파일을 무작정 선택하지 않습니다. 소스, 테스트 보고서, 실행 설정은 용도가 다릅니다. 배포 기록에는 배포한 파일의 SHA-256과 검증 결과를 연결합니다. 같은 파일을 다른 폴더로 복사했을 때 해시가 같으면 바이트가 같다는 근거가 되지만, 설정이나 DB 상태까지 같다는 뜻으로 확대하지 않습니다.

외부 설정으로 같은 파일을 재사용합니다

설정 예시는 jar 외부의 config 폴더에 있습니다. 실행할 때 APP_CONFIG가 가리키는 절대 위치를 SPRING_CONFIG_ADDITIONAL_LOCATION으로 전달합니다. Spring Boot가 그 파일을 추가 설정으로 읽고 CATALOG_JDBC_URL 환경변수를 catalog.jdbc.url에 사용합니다. 코드의 기본 URL은 이전 회귀 테스트를 위해 남겨 두지만 배포 실행기는 누락을 거절합니다. JVM 옵션은 -jar 앞, Spring 명령행 옵션은 jar 뒤에 놓는 차이도 살핍니다. 여러 곳에서 같은 키를 설정하면 우선순위로 실제 값이 바뀔 수 있으므로 이번 실습은 주입 위치를 좁혀 둡니다.

비밀값 분리와 DB 인증 구현은 다릅니다

외부 파일에 값을 두었다고 그 값이 암호화되거나 모든 프로세스에서 숨겨지는 것은 아닙니다. 파일의 읽기 권한과 로그 정책도 함께 필요합니다. 이 누적 프로젝트는 격리된 H2에서 sa와 빈 암호를 사용하는 학습용 계약을 유지합니다. 환경변수 이름만 DB_PASSWORD로 추가하고 코드에서 사용하지 않으면 인증을 분리한 것처럼 오해하게 됩니다. 여기서는 실제 코드가 소비하는 DB URL과 설정 위치만 주입합니다. 공개 서비스의 DB 계정·비밀 저장소 설계는 이 표본의 배포 성공으로 검증됐다고 주장하지 않습니다.

기동 문제를 빌드 문제와 구분합니다

no main manifest attribute는 일반 jar 또는 잘못된 패키징을 먼저 의심합니다. UnsupportedClassVersionError는 빌드한 클래스와 실행 JVM의 지원 버전을 대조합니다. NoClassDefFoundError는 실행 클래스패스나 패키징된 의존성을 살핍니다. HTTP 연결 실패는 jar 구조 외에도 기동 완료 여부, 포트, DB 권한을 확인해야 합니다. 오류 이름마다 조사하는 경계가 다르므로 테스트를 다시 돌리는 행동만 반복하지 않습니다. 이번 셸 단계에서는 서버를 상시 띄우지 않고 ZIP과 테스트로 산출물 근거를 만듭니다.

새로 만들기보다 검증한 산출물을 옮깁니다

리허설에서 통과한 뒤 실행 환경에 맞춘다고 다시 빌드하면 다른 파일이 만들어질 수 있습니다. 준비된 실행 jar와 실행 방법, 외부 설정의 이름, 필요한 데이터 경로를 한 묶음의 배포 계약으로 전달합니다. 제출할 때는 policy TODO 구현, 누적 보고서, jar 구조 검사 결과, 복사 전후 해시를 남깁니다. 정해진 버전을 쓴다는 설명은 바이트 단위 재현 빌드의 완전한 증명과 구별합니다. Maven 의존성 충돌과 다른 빌드 도구 비교는 더 읽기로 보내고 이 레슨에서는 실행 파일의 경계에 집중합니다.

실행 jar와 설정 우선순위의 기준은 Spring Boot 3.1.5 공식 문서에서 확인할 수 있습니다.

따라하기

정책 검사의 실패를 찾습니다

starter에서 테스트를 실행합니다. executableJarAccepted가 false를 받아 실패하는 위치를 찾습니다.

./mvnw test

실행 구조 규칙을 구현합니다

ArtifactPolicy.executableLayout에서 Main-Class가 org.springframework.boot.loader.JarLauncher이고 Start-Class가 lab.ApiApplication이며 nestedLibraries가 true인 경우만 승인합니다. 문자열 비교에는 equals를 사용합니다.

실제 jar를 생성합니다

학습자는 Maven package를 실행한 뒤 ZIP 검사를 실행합니다. 작성자는 준비된 캐시에서 -o 옵션으로 같은 빌드를 검증했습니다. 정상 종료 뒤 report.py로 집계합니다.

./mvnw package
python3 artifact-check.py

누적 보고서를 확인합니다

solution에서 package가 끝난 뒤 실행한 결과입니다. 본인의 보고서도 실패·오류·건너뜀 없이 160개인지 확인합니다.

python3 report.py

실행 결과

Tests=160
Failures=0
Errors=0
Skipped=0

ZIP 계약을 확인합니다

생성된 파일을 직접 검사한 결과입니다. 이 결과와 정책 단위 검사를 구별하여 제출합니다.

python3 artifact-check.py

실행 결과

PASS: executable jar layout and external configuration

확인 문제

실습

ArtifactPolicy.executableLayout의 TODO를 구현합니다. 이전 156개 검사를 보존하고 신규 4개 정책 검사를 통과시킵니다. ./mvnw package 후 python3 artifact-check.py로 실제 jar를 열어 확인하고 report.py 집계를 제출합니다.

시작 코드·테스트 내려받기

실행 명령

./mvnw test

기대 결과

Tests=160, Failures=0, Errors=0, Skipped=0. package와 artifact-check.py는 별도 실행합니다.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 실행 환경에서 API 오류를 추적하는 순서를 설명합니다.