본문 바로가기
빌드노트 검색

빌드노트

Maven filtering이 런타임용 ${...}까지 바꿀 때

Maven filtering이 런타임용 ${...}를 같은 이름의 빌드 property로 바꾸는 조건을 비교한다. 원본과 target 리소스를 읽고, 파일 분리와 escapeString 설정 중 선택하는 방법을 설명한다. 역슬래시만 붙인 경우와 설정까지 맞춘 경우의 차이도 실제 출력으로 확인했다.

Maven 빌드 뒤 텍스트 템플릿의 ${service.endpoint}가 특정 주소나 값으로 바뀌었다면, 먼저 원본과 target/classes의 같은 줄을 비교한다. 해당 파일에 리소스 filtering을 켰고 같은 이름의 Maven property가 있다면, 런타임에 남겨 두려던 표현식도 빌드 중 치환될 수 있다.

Maven은 그 표현식을 나중에 다른 템플릿 엔진이 읽을 예정인지 구분하지 않는다. 파일 전체를 그대로 전달할 수 있으면 filtering 대상에서 분리하고, 한 파일에 빌드용 값과 런타임용 표현식이 섞여 있으면 명시적인 escape 설정을 검토한다. 역슬래시 하나를 붙이는 것만으로 보호된다고 가정하면 안 된다.

원본에는 있는데 target에서는 사라졌는지 확인한다

다음은 Windows PowerShell에서 기존 파일의 runtime= 줄을 찾는 명령이다. 리소스를 소유한 모듈의 POM이 있는 디렉터리에서 실행하며, 두 경로와 검색할 키를 자신의 문제 파일에 맞춘다. 확인하려는 빌드가 만든 산출물을 대상으로 한다. 이미 있는 파일을 읽는 명령이므로 파일을 바꾸지 않는다.

Select-String -LiteralPath 'src/main/resources/template.txt','target/classes/template.txt' -SimpleMatch -Pattern 'runtime='

아래는 이 글의 별도 실험에서 filtering을 켜고 escape를 설정하지 않은 경우의 출력이다.

src\main\resources\template.txt:2:runtime=${service.endpoint}
target\classes\template.txt:2:runtime=BUILD_ENDPOINT

이 차이가 있으면 런타임이 파일을 읽기 전에 표현식이 사라진 것이다. POM과 활성 프로필에서 이 파일을 포함하는 <resource>의 <filtering>true</filtering>를 확인하고, service.endpoint라는 이름이 POM의 <properties>, 필터 파일, 빌드 명령의 -D 등에 있는지 찾는다. 공식 filtering 예제 (새 탭에서 열림)는 ${...}와 @...@의 값을 프로젝트·시스템 property, 필터 파일, 명령행에서 가져올 수 있다고 설명한다.

상속한 플러그인 설정에서 구분자를 바꿨는지도 함께 본다. ${...}를 치환 대상으로 인식하지 않도록 구성했다면 같은 이름의 property가 있어도 아래 실험과 조건이 다르다.

target/classes는 기본 출력 경로다. 출력 디렉터리를 바꿨다면 실제 경로를 읽어야 한다. 파일이 없으면 먼저 리소스 경로와 복사 대상을 확인한다. 원본과 출력에 모두 표현식이 남아 있다면, 이 비교로 Maven 치환을 원인이라고 할 수 없다. 그 뒤에 실행되는 템플릿 처리나 패키징 단계의 입력을 이어서 확인한다.

역슬래시만 붙이면 값 앞에 역슬래시가 남았다

Windows 11, Maven 3.9.16, JDK 17.0.2에서 독립 POM으로 확인했다. Maven Resources Plugin은 3.5.0, 텍스트 인코딩은 UTF-8로 고정하고 process-resources까지 실행했다. 실제 서비스에서 발생한 장애 사례가 아니라 치환 조건을 비교한 작은 실험이다. resources goal 문서 (새 탭에서 열림)는 이 goal이 리소스를 출력 디렉터리로 복사하며 기본적으로 process-resources 단계에 연결된다고 설명한다.

같은 파일 안에서 일반 표현식과 역슬래시를 붙인 표현식의 차이를 보려고 template.txt에 다음 세 줄을 넣었다. escaped라는 키는 비교용이며, 역슬래시는 파일에 실제로 한 개를 썼다.

build=${build.label}
runtime=${service.endpoint}
escaped=\${service.endpoint}

POM의 <properties>에는 <build.label>LAB_BUILD</build.label>와 <service.endpoint>BUILD_ENDPOINT</service.endpoint>를 넣었다. 따라서 두 이름 모두 Maven이 값을 찾을 수 있는 조건이다. 기본 구분자를 유지한 채 <filtering>과 플러그인의 <escapeString>만 바꾼 결과는 다음과 같았다.

  • filtering=false, escape 설정 없음: 세 줄 모두 원본 그대로였다.
  • filtering=false, escapeString을 \로 설정: 역시 원본 그대로였다. escaped 줄의 역슬래시도 남았다.
  • filtering=true, escape 설정 없음: build=LAB_BUILD, runtime=BUILD_ENDPOINT, escaped=\BUILD_ENDPOINT가 됐다.
  • filtering=true, escapeString을 \로 설정: build=LAB_BUILD, runtime=BUILD_ENDPOINT, escaped=${service.endpoint}가 됐다.

모든 조건에서 Maven은 정상 종료했다. 빌드용 값을 치환하면서 런타임용 표현식을 남긴 것은 마지막 조건의 escaped 줄뿐이었다. 역슬래시만 붙이면 치환은 진행됐고, escape 설정만 추가하면 역슬래시가 없는 runtime 줄은 여전히 치환됐다. 두 조건을 함께 맞춰야 했다.

빌드 때 바꿀 값이 없는 파일은 filtering에서 뺀다

템플릿 전체가 런타임용이라면 표현식마다 escape를 붙이기보다 파일을 그대로 복사하는 편이 단순하다. 빌드 정보도 필요하다면 파일을 나눈다. 실험에서는 template.txt에 runtime=${service.endpoint}만 두고, 별도 build-info.txt에 build=${build.label}를 뒀다.

다음 설정은 기존 <build> 안의 리소스 목록을 나눠, src/main/resources는 그대로 복사하고 src/main/resources-filtered만 치환한다. 런타임 템플릿은 앞 폴더에, 빌드 정보 파일은 뒤 폴더에 둔다.

<resources>
  <resource>
    <directory>src/main/resources</directory>
    <filtering>false</filtering>
  </resource>
  <resource>
    <directory>src/main/resources-filtered</directory>
    <filtering>true</filtering>
  </resource>
</resources>

이 구성으로 실행하자 target/classes/template.txt에는 runtime=${service.endpoint}가 남고, target/classes/build-info.txt에는 build=LAB_BUILD가 들어갔다. 두 파일의 치환 시점을 따로 정할 수 있었다. 필터링하지 않는 템플릿에는 Maven용 역슬래시를 추가하지 않았다.

폴더를 나눌 때는 기존의 포괄적인 filtering 항목도 함께 수정한다. 같은 파일을 다른 <resource>가 다시 필터링하거나, 두 소스 폴더에 같은 상대 경로의 파일이 있어서 출력이 겹치지 않게 한다. 런타임 템플릿과 빌드 정보를 반드시 한 파일에 둬야 한다면 이 분리 방식만으로 해결하기 어렵다.

한 파일에 두 시점의 값이 필요하면 escape를 명시한다

같은 파일의 빌드용 값은 계속 치환하면서 일부 표현식만 남기려면, 기존 <build><plugins>의 Resources Plugin 선언에 다음 설정을 합친다. 아래 3.5.0은 이 글에서 실행한 버전이며, 기존 선언을 중복 추가하지 않는다. 대상 <resource>의 filtering은 true로 유지한다.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-resources-plugin</artifactId>
  <version>3.5.0</version>
  <configuration>
    <encoding>UTF-8</encoding>
    <escapeString>\</escapeString>
  </configuration>
</plugin>

그다음 실제로 남길 줄을 runtime=\${service.endpoint}로 바꾼다. 비교용 escaped 줄을 제거한 최종 입력도 따로 실행했다. 빌드용 표현식에는 역슬래시를 붙이지 않았다.

build=${build.label}
runtime=\${service.endpoint}

process-resources 실행 후의 target/classes/template.txt는 다음과 같았다. 빌드 값은 치환되고, 런타임 표현식 앞의 역슬래시는 제거됐다.

build=LAB_BUILD
runtime=${service.endpoint}

공식 escape filtering 문서 (새 탭에서 열림)도 설정한 escape 문자열을 제거하고 표현식을 남기는 동작을 설명한다. 여기서 쓴 역슬래시 한 개는 XML 설정값과 원본 .txt에 직접 적은 문자다. Java 문자열, JSON 문자열, 셸을 거쳐 파일을 만드는 코드의 표기까지 모두 같다는 뜻은 아니다.

수정 뒤에는 실제 빌드에 쓰는 POM·프로필·옵션으로 process-resources를 다시 실행하고 처음의 파일 비교를 반복한다. escape 방식이라면 소스의 runtime=\${service.endpoint}가 출력의 runtime=${service.endpoint}가 되는지, 필요한 빌드 값도 계속 치환되는지 함께 본다. 출력에 역슬래시가 그대로 남으면 filtering이 꺼졌거나 해당 실행에 escape 설정이 적용되지 않았는지 확인한다.

이번 검증은 Resources Plugin 3.5.0이 한 번 처리한 텍스트 출력까지다. JAR 패키징과 런타임 템플릿 엔진의 실제 치환은 실행하지 않았다. 출력에 표현식을 보존했더라도 이후 다른 단계가 다시 ${...}를 처리한다면 그 단계의 입력과 escape 규칙을 별도로 확인해야 한다.

← 전체 글 보기