JAR 파일 포맷의 힘
Pagadala J. Suresh, 소프트웨어 엔지니어, IBM Global Services India Palaniyappan Thiagarajan, 소프트웨어 엔지니어, IBM Global Services India
2003년 10월 9일
대부분의 자바 프로그래머들은 JAR 파일의 기본 작동에 익숙하다. 하지만 JAR 파일 포맷의 막강한 힘을 아는 개발자는 드물다.
JAR 파일 JAR 파일 포맷은 대중적인 ZIP 파일 포맷을 근간으로 하여 많은 파일들을 하나로 모으는데 사용된다. ZIP 파일과는 달리 JAR 파일은 압축과 디스트리뷰션 뿐만 아니라 라이브러리, 컴포넌트, 플러그인 등의 전개와 캡슐화에도 사용되며 컴파일러나 JVM 같은 툴이 직접 사용하기도 한다. 전개 디스크립터 같이 JAR에 포함된 특별한 파일은 특정 JAR가 취급되는 방법을 툴에 지시한다.
JAR 파일은 다음과 같은 데에 사용된다:
- 클래스 라이브러리의 분배 및 사용
- 애플리케이션과 확장용 블록 구현
- 컴포넌트, 애플릿, 플러그인용 전개 유닛
- 컴포넌트와 관련된 보조 리소스 패키지
JAR 파일 포맷은 많은 혜택과 기능을 제공하며 ZIP 또는 TAR 같은 전통적인 아카이브 포맷이 줄 수 없는 많은 것들을 제공한다. 이를 테면:
- 보안. JAR 파일의 내용을 디지틀 방식으로 서명할 수 있다.
- 다운로드 시간 감소. 애플릿이 JAR 파일로 번들되면 이 애플릿의 클래스 파일과 관련 리소스들은 한 번의 HTTP 트랜잭션에서 브라우저를 통해 다운로드 될 수 있다.
- 압축. JAR 포맷은 파일의 효율적인 저장을 위해 압축시킨다.
- 투명한 플랫폼 확장. Java Extensions Framework은 자바 핵심 플랫폼에 기능을 추가할 수 있는 수단을 제공하는데 이 때, 확장의 패키지에 JAR 파일을 사용한다. (Java 3D와 JavaMail이 Sun에서 개발된 확장의 예이다.)
- 패키지 실링(sealing). JAR 파일에 저장된 패키지는 선택적으로 봉합(seal)되어 버전의 영속성과 보안을 강화할 수 있다. 패키지 봉합은 이 패키지에 정의된 모든 클래스들이 같은 JAR 파일에서 찾을 수 있어야 함을 의미한다.
- 패키지 버저닝(versioning). JAR 파일은 이것이 포함하고 있는 파일 관련 데이터를 보유하고 있다. 벤더와 버전 정보 등이다.
- 이식성(Portability). JAR 파일을 핸들링하는 메커니즘은 자바 플랫폼의 핵심 API의 표준의 일부이다.
JAR의 압축과 압축풀기
jar 툴(jar 툴 참조)은 파일을 기본적으로 압축한다. 압축이 풀린 JAR 파일은 압축된 JAR 파일 보다 더 빠르게 로딩될 수 있다. 로딩 시간 동안 파일의 압축 풀기 시간이 줄어들기 때문이다. 하지만 네트워크를 통한 다운로드 시간은 압축이 풀린 파일이 더 길다.
META-INF 디렉토리 대부분의 JAR 파일에는 META-INF 디렉토리가 포함되어 있는데 이는 패키지의 저장과 보안 및 버저닝 정보 같은 확장 설정 데이터를 저장하는데 사용된다. META-INF 디렉토리의 파일과 디렉토리는 Java2platform에서 인식 및 인터프리팅되어 애플리케이션, 확장, 클래스 로더를 설정한다:
- MANIFEST.MF. manifest 파일은 확장 관련, 패키지 관련 데이터를 정의한다.
- INDEX.LIST. 이 파일은
jar 툴의 새로운 -i 옵션에 의해 생성되어 애플리케이션 또는 확장에 정의된 패키지의 위치 정보를 포함한다. 이것은 JarIndex 구현의 일부이고 클래스 로더에 의해 사용되어 클래스 로딩 프로세스의 속도를 높인다.
- xxx.SF. JAR 파일의 서명 파일이다. xxx는 서명자를 나타낸다.
- xxx.DSA. 서명 파일과 관련된 서명 블록 파일은 JAR 파일의 서명에 사용된 공식 서명을 저장한다.
jar 툴 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 파일은 특별히 설정된 JAR 파일에 저장된 독립적인 자바 애플리케이션이다. 파일을 추출하거나 클래스 경로를 설정하지 않고 JVM에 의해 직접 실행될 수 있다. 비 실행 JAR에 저장된 애플리케이션을 구동하려면 이를 클래스 경로에 추가하고 애플리케이션의 메인 클래스를 이름별로 호출해야한다. 하지만 실행 JAR 파일을 사용하면 이를 추출하거나 메인 엔트리 포인트를 알 필요 없이 애플리케이션을 실행할 수 있다.
실행 JAR 파일 만들기 실행 JAR 파일을 만들기는 쉽다. 모든 애플리케이션 코드를 하나의 디렉토리에 놓는 것으로 시작한다. 애플리케이션의 메인 클래스가 com.mycompany.myapp.Sample 이라고 가정해보자. 애플리케이션 코드를 포함하고 메인 클래스를 구분하는 JAR 파일 생성이 필요하다. 이를 위해 라는 manifest 파일을 어딘가에(애플리케이션 디렉토리는 아니다) 만들고 여기에 다음 행을 추가한다:
Main-Class: com.mycompany.myapp.Sample
|
그런 다음 JAR 파일을 다음과 같이 만든다:
jar cmf manifest ExecutableJar.jar application-dir
|
이제 JAR 파일인 ExecutableJar.jar가 java -jar 를 사용하여 실행될 수 있다.
실행 JAR 파일 시작하기 애플리케이션을 ExecutableJar.jar라는 실행 JAR 파일로 묶었으므로 다음 명령어를 사용하여 파일에서 직접 애플리케이션을 시작할 수 있다:
java -jar ExecutableJar.jar
|
패키지 실링(sealing) JAR 파일안에 패키지를 봉합(sealing)한다는 것은 이 패키지에 정의된 모든 클래스가 같은 JAR 파일에서 찾아져야 한다는 것을 의미한다. 이로서 패키지 작성자는 패키지된 클래스들의 버전 영속성을 강화할 수 있다. 봉합은 보안 조치도 제공하여 코드 탬퍼링을 탐지한다.
패키지를 봉합하려면 패키지용 Name 헤더를 추가한다. 그 뒤에 Sealed 헤더 값을 JAR manifest 파일에 대해 "true"로 한다. 실행 JAR 파일과 마찬가지로 manifest 파일을 적절한 헤더 엘리먼트로 지정하여 JAR를 봉합할 수 있다:
Name: com/samplePackage/
Sealed: true
|
Name 헤더는 패키지의 관련 경로명을 정한다. 파일이름과 구별되도록 "/"로 끝난다. Name 헤더에 뒤따르는 모든 헤더는 공백 라인 없이 Name 헤더에 지정된 파일이나 패키지에 붙는다. 위 예제에서 Sealed 헤더가 공백 라인 없이 Name 헤더 다음에 발생했기 때문에 Sealed 헤더는 com/samplePackage 패키지에만 붙는것으로 인터프리팅된다.
JAR 파일 외에 다른 소스에서 봉합된 패키지의 클래스를 로딩하려고 하면 JVM이 SecurityException 을 던진다.
확장 패키징 확장은 자바 플랫폼에 기능을 추가한다. 확장 메커니즘은 JAR 파일 포맷에 구현된다. 확장 메커니즘으로 JAR 파일이 다른 필요한 JAR 파일들을 Class-Path 헤더를 통해 manifest 파일에 지정할 수 있다.
extension1.jar와 extension2.jar가 같은 디렉토리 안의 두 개의 JAR 파일에 있다고 가정해보자. extension1.jar의 manifest는 다음 헤더를 포함하고 있다:
Class-Path: extension2.jar
|
이 헤더는 extension2.jar의 클래스들이 extension1.jar의 클래스를 목표에 맞춘 확장 클래스로서 작용한다는 것을 나타내고 있다. extension1.jar의 클래스들은 extension2.jar가 클랫의 경로의 일부가 될 필요 없이 extension2.jar의 클래스를 호출할 수 있다.
JVM은 확장 메커니즘을 사용하는 JAR를 로딩할 때 Class-Path 헤더에 레퍼런스된 JAR를 클래스 경로에 자동으로 추가한다. 하지만, 확장 JAR 경로는 관련 경로로 인터프리팅되어 일반적으로 확장 JAR는 이를 레퍼런싱하는 JAR로서 같은 디렉토리에 저장되어야 한다.
예를 들어 ExtensionDemo 클래스를 레퍼런싱하는 ExtensionClient 클래스가 ExtensionClient.jar라고 하는 JAR 파일에 번들되었고 ExtensionDemo 클래스가 ExtensionDemo.jar에 번들되었다고 가정해보자. ExtensionDemo.jar가 확장으로 취급되기 위해서는 ExtensionDemo.jar는 ExtensionClient.jar의 manifest 안의 Class-Path 헤더에 리스트되어야 한다:
Manifest-Version: 1.0
Class-Path: ExtensionDemo.jar
|
Class-Path 헤더의 값은 경로가 지정이 안된 ExtensionDemo.jar 이며 ExtensionDemo.jar가 ExtensionClient JAR 파일과 같은 디렉토리에 위치해 있음을 나타내고 있다.
JAR 파일의 보안 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와 서명 파일
Contents of signature file META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.3.0 (Sun Microsystems Inc.)
Name: Sample.java
SHA1-Digest: 3+DdYW8INICtyG8ZarHlFxX0W6g=
Name: Sample.class
SHA1-Digest: YJ5yQHBZBJ3SsTNcHJFqUkfWEmI=
Contents of signature file META-INF/JAMES.SF
Signature-Version: 1.0
SHA1-Digest-Manifest: HBstZOJBuuTJ6QMIdB90T8sjaOM=
Created-By: 1.3.0 (Sun Microsystems Inc.)
Name: Sample.java
SHA1-Digest: qipMDrkurQcKwnyIlI3Jtrnia8Q=
Name: Sample.class
SHA1-Digest: pT2DYby8QXPcCzv2NwpLxd8p4G4=
|
디지틀 서명 디지틀 서명은 .SF 서명 파일의 서명완료된 버전이다. 디지틀 서명 파일은 바이너리 파일이며 .SF 파일과 같은 파일이름을 갖고 있지만 다른 확장이다. 확장은 디지틀 서명 유형에 따라 다양하고 (RSA, DSA, PGP). JAR 서명에 사용된 인증 유형에 따라 다르다.
Keystore JAR 파일에 서명하려면 프라이빗 키를 가져야 한다. 프라이빗 키와 관련 퍼블릭 키 인증은 패스워드로 보호된 데이터베이스(keystores )에 저장된다. JDK는 Keystore를 구현 및 변경하는 툴을 포함하고 있다. Keystore의 각 키는 앨리어스에 의해 구분되는데 전형적으로 키를 소유한 서명자의 이름이다.
모든 Keystore 엔트리들은 고유 앨리어스로 액세스된다. 앨리어스는 Keystore에 엔터티를 추가할 때 keytool -genkey 명령어를 사용하여 지정되어 키 쌍을 만든다. 뒤따르는 keytool 명령어는 이와 같은 앨리어스를 사용하여 엔터티를 언급해야 한다.
예를 들어 "james"라는 앨리어스로 새로운 퍼블릭/프라이빗 키 쌍을 만들고 퍼블릭 키를 자가 서명된 인증으로 래핑하려면 다음 명령어를 사용한다:
keytool -genkey -alias james -keypass jamespass
-validity 80 -keystore jamesKeyStore
-storepass jamesKeyStorePass
|
jarsigner 툴
jarsigner 툴은 Keystore를 사용하여 JAR 파일에 대한 디지틀 서명을 만들거나 확인한다.
위 예제에서 처럼 "jamesKeyStore" Keystore를 만들었고 여기에 "james" 앨리어스와 키를 포함하고 있다고 가정해보자. 다음 명령어로 JAR 파일에 서명할 수 있다:
jarsigner -keystore jamesKeyStore -storepass jamesKeyStorePass
-keypass jamespass -signedjar SSample.jar Sample.jar james
|
이 명령어는 앨리어스가 "james"이고 패스워드가 "jamespass"인 키를 보내 Sample.jar 파일에 서명하고 SSample.jar라는 서명된 JAR를 만든다.
jarsigner 툴은 서명된 JAR 파일을 확인할 수 있다. 이 작동은 JAR 파일을 서명하는 것 보다 훨씬 쉽다. 다음 명령어를 실행하면 된다:
jarsigner -verify SSample.jar
|
JAR 인덱싱(indexing) 애플리케이션 또는 애플릿이 다중의 JAR 파일들로 번들된다면 클래스 로더는 단순한 리니어 검색 알고리즘을 사용하여 클래스 경로의 엘리먼트를 검색한다. 클래스 로더가 존재하지 않은 리소스를 찾으려고 하면 애플리케이션 또는 애플릿 내의 모든 JAR 파일들은 다운로드 되어야한다. 큰 네트워크 애플리케이션과 애플릿의 경우 늦은 시작, 지연된 응답, 네트워크 대역 낭비를 초래한다.
JDK 1.3 이후 JAR 파일 포맷은 인덱싱(indexing)을 지원하여 네트워크 애플리케이션(특히 애플릿)의 클래스 검색 프로세스를 최적화했다. JarIndex 메커니즘은 애플릿 또는 애플리케이션에 정의된 모든 JAR 파일의 내용을 모아 첫 번째 JAR 파일의 인덱스 파일에 이 정보를 저장한다. 첫 번째 JAR 파일이 다운로드된 후에 애플릿 클래스 로더는 모아진 콘텐트 정보를 사용하여 JAR 파일을 효율적으로 다운로드한다. 이 디렉토리 정보는 INDEX.LIST라는 이름으로 간단한 텍스트 파일로 저장된다.(META-INF 디렉토리).
JarIndex 만들기
그림 2. JarIndex
다음 명령어를 사용하여 JarIndex_Main.jar, JarIndex_test.jar, JarIndex_test1.jar용 인덱스 파일을 만든다:
jar -i JarIndex_Main.jar JarIndex_test.jar SampleDir/JarIndex_test1.jar
|
INDEX.LIST 파일은 간단한 포맷을 갖고 있으며 색인된 JAR 파일에 저장된 패키지 또는 클래스 이름을 포함하고 있다.(Listing 2): Listing 2. JarIndex INDEX.LIST 파일
JarIndex-Version: 1.0
JarIndex_Main.jar
sp
JarIndex_test.jar
Sample
SampleDir/JarIndex_test1.jar
org
org/apache
org/apache/xerces
org/apache/xerces/framework
org/apache/xerces/framework/xml4j
|
참고자료
|