jar 파일 포맷의 힘
* 본 글은 저의 저작물이 아니며 그런 이유로 제 블로그에 포스팅 하는 글에 대한 CCL의 영향을 받지 않습니다. *
대부분의 자바 프로그래머들은 JAR 파일의 기본 작동에 익숙하다. 하지만 JAR 파일 포맷의 막강한 힘을 아는 개발자는 드물다.
JAR 파일 포맷은 대중적인 ZIP 파일 포맷을 근간으로 하여 많은 파일들을 하나로 모으는데 사용된다. ZIP 파일과는 달리 JAR 파일은 압축과 디스트리뷰션 뿐만 아니라 라이브러리, 컴포넌트, 플러그인 등의 전개와 캡슐화에도 사용되며 컴파일러나 JVM 같은 툴이 직접 사용하기도 한다. 전개 디스크립터 같이 JAR에 포함된 특별한 파일은 특정 JAR가 취급되는 방법을 툴에 지시한다.
JAR 파일은 다음과 같은 데에 사용된다:
JAR 파일 포맷은 많은 혜택과 기능을 제공하며 ZIP 또는 TAR 같은 전통적인 아카이브 포맷이 줄 수 없는 많은 것들을 제공한다. 이를 테면:
jar
툴( jar
툴 참조)은 파일을 기본적으로 압축한다. 압축이 풀린 JAR 파일은 압축된 JAR 파일 보다 더 빠르게 로딩될 수 있다.
로딩 시간 동안 파일의 압축 풀기 시간이 줄어들기 때문이다. 하지만 네트워크를 통한 다운로드 시간은 압축이 풀린 파일이 더 길다.
대부분의 JAR 파일에는 META-INF 디렉토리가 포함되어 있는데 이는 패키지의 저장과 보안 및 버저닝 정보 같은 확장 설정 데이터를 저장하는데 사용된다. META-INF 디렉토리의 파일과 디렉토리는 Java2platform에서 인식 및 인터프리팅되어 애플리케이션, 확장, 클래스 로더를 설정한다:
JAR 파일로 기본적인 태스크를 수행하려면 자바 개발 킷의 일부로 제공되는 Java Archive Tool (jar
툴)을 사용한다. jar 툴을 jar
명령어로 호출한다. 표 1은 일반 애플리케이션이다:
표 1. jar 툴의 일반적인 사용
기능 | 명령어 |
개별 파일에서 JAR 파일 만들기 | jar cf jar-file input-file... |
디렉토리에서 JAR 파일 만들기 | jar cf jar-file dir-name |
압축 풀린 JAR 파일 만들기 | jar cf0 jar-file dir-name |
JAR 파일 업데이트 | jar uf jar-file input-file... |
JAR 파일 내용보기 | jar tf jar-file |
JAR 파일 내용 추출하기 | jar xf jar-file |
JAR 파일에서 특정 파일 추출하기 | jar xf jar-file archived-file... |
실행 JAR 파일로 패키지된 애플리케이션 실행하기 | java -jar app.jar |
|
실행 JAR 파일은 특별히 설정된 JAR 파일에 저장된 독립적인 자바 애플리케이션이다. 파일을 추출하거나 클래스 경로를 설정하지 않고 JVM에 의해 직접 실행될 수 있다. 비 실행 JAR에 저장된 애플리케이션을 구동하려면 이를 클래스 경로에 추가하고 애플리케이션의 메인 클래스를 이름별로 호출해야한다. 하지만 실행 JAR 파일을 사용하면 이를 추출하거나 메인 엔트리 포인트를 알 필요 없이 애플리케이션을 실행할 수 있다.
실행 JAR 파일을 만들기는 쉽다. 모든 애플리케이션 코드를 하나의 디렉토리에 놓는 것으로 시작한다. 애플리케이션의 메인 클래스가
com.mycompany.myapp.Sample
이라고 가정해보자. 애플리케이션
코드를 포함하고 메인 클래스를 구분하는 JAR 파일 생성이 필요하다. 이를 위해 라는 manifest
파일을 어딘가에(애플리케이션 디렉토리는 아니다) 만들고 여기에 다음 행을
추가한다:
|
그런 다음 JAR 파일을 다음과 같이 만든다:
|
애플리케이션을 ExecutableJar.jar라는 실행 JAR 파일로 묶었으므로 다음 명령어를 사용하여 파일에서 직접 애플리케이션을 시작할 수 있다:
|
|
JAR 파일안에 패키지를 봉합(sealing)한다는 것은 이 패키지에 정의된 모든 클래스가 같은 JAR 파일에서 찾아져야 한다는 것을 의미한다. 이로서 패키지 작성자는 패키지된 클래스들의 버전 영속성을 강화할 수 있다. 봉합은 보안 조치도 제공하여 코드 탬퍼링을 탐지한다.
패키지를 봉합하려면 패키지용 Name
헤더를 추가한다. 그 뒤에
Sealed
헤더 값을 JAR manifest 파일에 대해 "true"로 한다.
실행 JAR 파일과 마찬가지로 manifest 파일을 적절한 헤더 엘리먼트로 지정하여 JAR를 봉합할 수 있다:
|
Name
헤더는 패키지의 관련 경로명을 정한다. 파일이름과 구별되도록
"/"로 끝난다. Name
헤더에 뒤따르는 모든 헤더는 공백 라인 없이
Name
헤더에 지정된 파일이나 패키지에 붙는다. 위 예제에서
Sealed
헤더가 공백 라인 없이 Name
헤더 다음에 발생했기 때문에 Sealed
헤더는 com/samplePackage
패키지에만 붙는것으로 인터프리팅된다.
확장은 자바 플랫폼에 기능을 추가한다. 확장 메커니즘은 JAR 파일 포맷에 구현된다. 확장 메커니즘으로 JAR 파일이 다른 필요한 JAR
파일들을 Class-Path
헤더를 통해 manifest 파일에 지정할 수
있다.
extension1.jar와 extension2.jar가 같은 디렉토리 안의 두 개의 JAR 파일에 있다고 가정해보자. extension1.jar의 manifest는 다음 헤더를 포함하고 있다:
|
이 헤더는 extension2.jar의 클래스들이 extension1.jar의 클래스를 목표에 맞춘 확장 클래스로서 작용한다는 것을 나타내고 있다. extension1.jar의 클래스들은 extension2.jar가 클랫의 경로의 일부가 될 필요 없이 extension2.jar의 클래스를 호출할 수 있다.
예를 들어 ExtensionDemo
클래스를 레퍼런싱하는
ExtensionClient
클래스가 ExtensionClient.jar라고 하는
JAR 파일에 번들되었고 ExtensionDemo
클래스가
ExtensionDemo.jar에 번들되었다고 가정해보자. ExtensionDemo.jar가 확장으로 취급되기 위해서는
ExtensionDemo.jar는 ExtensionClient.jar의 manifest 안의 Class-Path
헤더에 리스트되어야 한다:
|
|
JAR 파일은 jarsigner
툴을 사용하거나 java.security
API를 통해서 직접 서명될 수 있다. 서명된 JAR 파일은 원래 JAR
파일과 정확히 같다. manifest만이 업데이트 된 것과 두 개의 추가 파일들이 META-INF 디렉토리에 추가된 것을 제외하고.
Keystore 데이터베이스에 저장된 인증을 사용하여 JAR 파일은 서명된다. Keystore에 저장된 인증은 패스워드로 보호된다.
그림 1. Keystore 데이터베이스
JAR의 각 서명자는 JAR 파일의 META-INF 디렉토리안에 있는 .SF 확장자가 붙은 서명으로 표현된다. 이 파일의 포맷은 manifest 파일과 비슷하다. 메인 섹션과 개별 엔트리들로 구성되어 있다. 서명된 JAR에서 오는 파일을 확인하기 위해 서명 파일의 다이제스트 값은 JAR 파일의 상응 엔트리에 대비하여 계산된 다이제스트와 비교된다.
Listing 1. Manifest와 서명 파일
|
디지틀 서명은 .SF
서명 파일의 서명완료된 버전이다. 디지틀 서명 파일은
바이너리 파일이며 .SF
파일과 같은 파일이름을 갖고 있지만 다른 확장이다.
확장은 디지틀 서명 유형에 따라 다양하고 (RSA, DSA, PGP). JAR 서명에 사용된 인증 유형에 따라 다르다.
JAR 파일에 서명하려면 프라이빗 키를 가져야 한다. 프라이빗 키와 관련 퍼블릭 키 인증은 패스워드로 보호된
데이터베이스(keystores
)에 저장된다. JDK는 Keystore를 구현 및
변경하는 툴을 포함하고 있다. Keystore의 각 키는 앨리어스에 의해 구분되는데 전형적으로 키를 소유한 서명자의 이름이다.
모든 Keystore 엔트리들은 고유 앨리어스로 액세스된다. 앨리어스는 Keystore에 엔터티를 추가할 때 keytool -genkey
명령어를 사용하여 지정되어 키 쌍을 만든다. 뒤따르는 keytool
명령어는 이와 같은 앨리어스를 사용하여 엔터티를 언급해야 한다.
예를 들어 "james"라는 앨리어스로 새로운 퍼블릭/프라이빗 키 쌍을 만들고 퍼블릭 키를 자가 서명된 인증으로 래핑하려면 다음 명령어를 사용한다:
|
jarsigner
툴은 Keystore를 사용하여 JAR 파일에 대한 디지틀
서명을 만들거나 확인한다.
위 예제에서 처럼 "jamesKeyStore" Keystore를 만들었고 여기에 "james" 앨리어스와 키를 포함하고 있다고 가정해보자. 다음 명령어로 JAR 파일에 서명할 수 있다:
|
이 명령어는 앨리어스가 "james"이고 패스워드가 "jamespass"인 키를 보내 Sample.jar 파일에 서명하고 SSample.jar라는 서명된 JAR를 만든다.
|
|
애플리케이션 또는 애플릿이 다중의 JAR 파일들로 번들된다면 클래스 로더는 단순한 리니어 검색 알고리즘을 사용하여 클래스 경로의 엘리먼트를 검색한다. 클래스 로더가 존재하지 않은 리소스를 찾으려고 하면 애플리케이션 또는 애플릿 내의 모든 JAR 파일들은 다운로드 되어야한다. 큰 네트워크 애플리케이션과 애플릿의 경우 늦은 시작, 지연된 응답, 네트워크 대역 낭비를 초래한다.
JDK 1.3 이후 JAR 파일 포맷은 인덱싱(indexing)을 지원하여 네트워크 애플리케이션(특히 애플릿)의 클래스 검색 프로세스를 최적화했다. JarIndex 메커니즘은 애플릿 또는 애플리케이션에 정의된 모든 JAR 파일의 내용을 모아 첫 번째 JAR 파일의 인덱스 파일에 이 정보를 저장한다. 첫 번째 JAR 파일이 다운로드된 후에 애플릿 클래스 로더는 모아진 콘텐트 정보를 사용하여 JAR 파일을 효율적으로 다운로드한다. 이 디렉토리 정보는 INDEX.LIST라는 이름으로 간단한 텍스트 파일로 저장된다.(META-INF 디렉토리).
다음 명령어를 사용하여 JarIndex_Main.jar, JarIndex_test.jar, JarIndex_test1.jar용 인덱스 파일을 만든다:
|
INDEX.LIST 파일은 간단한 포맷을 갖고 있으며 색인된 JAR 파일에 저장된 패키지 또는 클래스 이름을 포함하고 있다.(Listing 2):
Listing 2. JarIndex INDEX.LIST 파일
|