Java2005. 3. 22. 10:50
출처: http://www-903.ibm.com/developerworks/kr/java/library/j-jar.html
JAR 파일 포맷의 힘
Level: Introductory
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 데이터베이스
Keystore Database
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 Demo
다음 명령어를 사용하여 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
참고자료
목 차:
JAR 파일
실행 JAR 파일
패키지 실링(sealing)
JAR 파일의 보안
JAR 인덱싱(indexing)
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
IBM WebSphere Application server and JAR file format
Java Web Start
Subscribe to the developerWorks newsletter
US 원문 읽기
필자소개
Photo of Pagadala SureshPagadala J. Suresh: 소프트웨어 엔지니어, IBM Global Services India.

Photo of Palaniyappan ThiagarajanPalaniyappan Thiagarajan: 소프트웨어 엔지니어, IBM Global Services India.



2004-04-23 14:42:48.0 (as9000 220.69.185.177) D

저도 오늘 이 내용을 읽었는데 괜찮더군요...
역시 개발자는 기본에 충실해야 하는데...
저도 어떤 기술이 있으면 대충 한번 훑어보고 마는 습관이 있는데..
jar 이용법을 보면서...자바를 한지 6년이 되어가는데 아직 이런것도 모른다는 생각에...--;
암튼...기본을 충실히 합시다...
그럼 다들 좋은 하루 되시길...^^;
Posted by la30321
Java2005. 3. 22. 10:48
심우곤님의 코딩 관습에 대한 기사가 업데이트 되었습니다. 저도 꽤 좋은 관습을 지키지 않는것이 많았습니다.

여러분도 확인해 보세요.


 

Java 코딩 지침 by wgshim (그린벨시스템즈 정보기술연구소)
 
본 지침은 여타의 지침들에 비하여 프로젝트의 코드 검토 단계에 사용하기 용이하도록 각 항목마다 주석과 예제를 두었습니다. 추가적으로, 일반적으로 프로그래밍 권고안들은 코딩 스타일 이슈와 언어 고유한 기술적 이슈들을 혼합하여 기술함으로써 혼란을 초래하는 경향이 있기 때문에, 본 문서에서는 Java 언어의 기술적인 권고안에 대해서 일절 언급하지 않고 오로지 프로그래밍 스타일에 대해서만 다루고자 합니다.(4/14)
Posted by la30321
Java2005. 3. 22. 10:40
지금까지 인터페이스는 메소드만 정의 되는줄 알았습니다. 정적 상수 형태는 인터페이스에서 정의가 가능합니다. C++과 자바 사이에서 혼란을 겪는 혼돈의 시기인가 봅니다.

public interface IMessageProvider {
/**
* Constant for a regular message (value 0).
* <p>
* Typically this indicates that the message should be
* shown without an icon.
* </p>
*/
public final static int NONE = 0;
/**
* Constant for an info message (value 1).
*/
public final static int INFORMATION = 1;
/**
* Constant for a warning message (value 2).
*/
public final static int WARNING = 2;
/**
* Constant for an error message (value 3).
*/
public final static int ERROR = 3;
}


2004-04-08 15:40:18.0 (highfive 203.224.101.26) D

저도 별 생각 없이 static 상수 선언이 안되는줄 알고 있었다가 얼마전 마소의 타이거 관련 기사에서 static import 부분을 보다가 깨달았습니다.
의외로 당연하다고 생각하고 넘어간 데에 함정이 많이 있더군요...

2004-04-09 08:53:07.0 (29zu 211.196.185.77) D

^^; 아.. 의외입니다...(왠지 인간적인면이 보인다는)
저같은 경우엔 C같은 언어도 학교때 하긴했지만 자바를 본격적으로 했기
때문에,C나 C#같은 부분을 볼때자바관점으로 바라보는 경향으로 인해
위의 경우와 같은 차이점을 놓치는 경우가 발생할듯합니다.
(실제 C#보다가 그런적이 있구요)

가끔씩 심심하면 자바책 기초를 훑어보는 경우가 있는데
그때 그동안 잘 몰랐던것을 알게되는경우가 많더라구요.



2004-04-09 15:04:21.0 (jini 61.40.188.229) D

1.5에서 인터페이스에서 필드 정의 가능을 바랬습니다.

아쉽게도 없네요.

공동작업에서 인터페이스로 프레임을 정해주면 매우 편리합니다. 필드도 포함된다면 매우 좋을것 같습니다.

아직 자바는 모르는 부분이 많습니다. 제이랩 유저분들의 많은 조언, 가르침 부탁드립니다.

2004-04-21 11:11:35.0 (이너버스 211.108.44.249) D

인터페이스에서 상수에 final static을 선언 안하셔도 기본적으로 final static 상수로 선언됩니다.



2004-04-21 21:29:18.0 (jini 219.248.234.155) D

그냥 int NONE =0; 이런식으로 써도 되는건가요?

2004-04-22 09:57:05.0 (이너버스 211.108.44.249) D

네.. 그리고 메소드나 멤버변수에 public을 안쓰셔도기본 public입니다.

2004-05-20 23:37:37.0 (papilla 220.122.230.33) D

인터페이스에 필드 정의 기능이 절!대!로!있어서는 안됩니다.

인터페이스란 한 컴포넌트가 다른 컴포넌트를 바라보는 관점을 기술하는 것입니다. 따라서, Interface 설계는역할 정의 작업이라고 할 수 있습니다.

//역할 명
interface 선생{
//역할 지위를 획득하기 위한 조건들
public void 학생을 가르친다();
public void 학생을 교화한다();
}

같은 역할에 대해, 그 역할수행자들에게 가시성을 가진다른 속성이 존재할 수는 없습니다. 왜냐하면 다른 속성이 존재한 다면, 그 속성은 이미 역할과 무관한 문제가 되어버리기 때문입니다!

interface 선생{
private int 하루 식사량;

//역할 지위를 획득하기 위한 조건들
public void 학생을 가르친다();
public void 학생을 교화한다();
}

그냥 보기에도 어처구니가 없지 않습니까? 식사량은 선생이라는 역할을 정의하는데에 있어서 어떠한 일도 수행하지 못합니다. 물론 월급이나 담당과목과 같은 공통 속성을 넣으면 되지 않느냐 하고 반문을 가질 수 있습니다. 하지만 그것은 선생역할을 수행할 수 있는 객체는 담당과목에 대한 데이터를 어떻게 추상화하여 저장해야 하는 것을 "강제"하는 결과를 초래합니다. 이것은 더 이상 다형성과, 로컬라이즈를 지원할 수 없음을 의미합니다. Interface는 단지 역할만을 정의해야 하므로, 구현 내용까지 강제해서는 안되는 것입니다.

역할은 역할을 수행하기 위해 필요한 것들만 제시할 뿐, 그것들의 구현은 자유롭게 풀어 둡니다. 그렇게 하는 것은 재사용성을 최대화며, 자유롭게 구현하게 함으로써 하기 위해서입니다.

ps. 굳이 그것이 필요하다면 그것을 완벽하게 지원 할 수 있는 abstract class가 있습니다. 하지만, 저는 이것의 사용을 완고히 반대하는 바입니다. 이것은 소규모의 프로젝트에서 확고한 다른 장점이 발견될 경우에만 사용해야 하며, 결코 일반화된 문제 해결 도구가 아님을 알아 두셔야 합니다. 이것은 class와 interface의 중간적 존재일 뿐입니다. 또, interface가 static field를 제공하기는 하지만 이것역시 사용해서는 안됩니다. 팔렛트나 폰트처럼 자주 사용되는 값만을 담고 있다 하더라도, 그것은 역할을 구현하는 방법에 간섭을 가지게 되고, 그것은 이 인터페이스와, 이 인터페이스를 구현하는 객체들 사이에 응집성이 존재해버리고 마는 우를 범하게 된다는 뜻이 됩니다.

예를 들어 JTree에 사용되는 트리모델의 트리 노드들이 private ImageIcon같은 것이 존재한다면 Swing차원에서는 굉장히 편리하게 생각될수도 있겠지만, 그것은 시각적 효과를 포함한 Tree모델이 되어 버리는 것이므로, 일반 문제 풀이에 더 이상 트리를 사용할 수 없게 되어버립니다.


2004-05-20 23:45:15.0 (jini 219.248.234.149) D

너무 비약적인 예인것 같습니다. 이런 논리를 전개 하면 모두 안좋은것으로 풀이되겠네요.

"인터페이스란 한 컴포넌트가 다른 컴포넌트를 바라보는 관점을 기술하는 것입니다. 따라서, Interface 설계는역할 정의 작업이라고 할 수 있습니다."

이렇게 정의 내린 사람은 없습니다. 언어에서의 역활은 컴퓨팅 환경과 개발자에 요구에 따라서 변화하는것입니다. 좀더 넓은 마음으로 언어를 대하는것이 어떨까요? 언어의 종류는 다양합니다.

2004-05-21 05:51:22.0 (papilla 220.122.230.33) D

우선 제 입장에서는 너무나 당연한 것이라 논리의 전개가 적절한 형태를 갖추지 못했다는 것을 이해합니다.

1. 왜 interface는 관점인가

다음의 예시를 봐 주세요.

어떠한 someObject가 MouseListener 인터페이스를 구현했다고 가정합시다. 물론 someObject는 MouseListener가 제시한 인터페이스 이외에도 독자적으로 가시성을 가진 기능을 제공하며 메시지에 반응 할 수 있을 것입니다.

그러나 다음의 코드를 봅시다.

MouseListener view = (MouseListener)someObject;

이제부터 view라는 네임을 통하여 MouserListener관점으로 someObject를 바라 보게 됩니다. 따라서 현재 스레드에게 있어 view란 단지 MouseListener의 역할을 수행할수 있는 어떠한 객체 일뿐, someObject의 실체에는 관심도 없을 뿐더러, MouseListener에 제시되지 않은 다른 수행 능력에 대해서는 관심도 없고, 가시성 조차 가지고 있지 않습니다. 심지어는 그러한 관점에 제시된 메시지가 실제로 어떻게 처리되는지 조차도 관심을 가지지 않습니다.

이러한 연유로, interface는 대상 객체에 대한 가시성과 인식을 결정하게 됩니다. 그 인식이란 대상객체가 MouseListener라는 역할자 라는 사실입니다. (UML에서는 Actor라고 부르지만 이것은 해석에 있어 오해의 소지가 많기 때문에 저는 Role Player라고 가르칩니다.) 따라서 interface는 컴포넌트 사이의 관점으로 해석 될 수 있으며, 그것이 interface가 가지는 가장 중요한 속성입니다. interface나 connector의 단어자체가 가진 의미이기도 합니다.


2. 인터페이스가 멤버 필드를 가져서는 안되는 이유

그러면 이제부터 왜 interface가 멤버 필드를 가져서는 안되는 지 설명하겠습니다. 우선은 너무나 당연한 이유가 있습니다. "왜 객체의 속성을 관점의 속성(역할의 속성)에 집어 넣으려고 하는가?" 그러면 다음과 같은 반박을 예상 할 수 있습니다. 이미 클래스나, 추상 클래스에서는 그러한 것을 제공하고 있지 않는가? 단지 같은 역할을 제공한다고 하고 전혀 다른 객체라 할지라도 공통적인 속성을 가질수도 있지 않는가? 그것은 Interface가 관점이라는 사실을 이해하지 못하기 때문에 나오는 반문입니다. 이부분을 명확히 하기 위하여, 실제로 그러한 것들을 제공하는 class와 비교해 보겠습니다. Abstract Class나 Class는 관점 뿐만 아니라 더 많은 것을 제공합니다. 그 관점에 따른 역할의 수행방법까지 명시적으로 제시되곤 하죠. 또 자식에게 멤버 필드를 제공할 수도 있습니다.


2.1 인터페이스와 추상 클래스의 차이
인터페이스와 클래스는 역할이 완전히 다르다는 사실을 알아야만 합니다. 클래스 는 역할을 정의할 뿐만 아니라 역할수행방법까지 명시하고 있습니다. 완벽히 인스턴스에 대해서 규정하고 있는 것입니다. 이것은 interface에 비해 훨씬 더 덜 추상화된 정의 방법이며 훨씬 더 restrict하며 specific합니다.

따라서 "클래스로 부터 상속을 받는 것"은 "그 역할과 역할 수행 방법"을 확장하여, 더 많은 기능이나 특화된 기능을 제공하는 클래스를 만들겠다 라는 의미이며, "인터페이스를 구현하겠다"라는 것은 "해당 인터페이스가 규정하는 역할을 추가적으로 구현 하여 그 역할'도' 획득하겠다"라는 의미인 것입니다. 따라서 인터페이스의 구현이란 추가적 관점의 획득일 뿐으로, 상속 체계상의 요소가 아닌 것입니다. Java는 강력하게 다중상속을 부정하고 있으며, Interface역시 다중 상속을 가능하게 위한 도구일리가 없지요! 잘못된 서적에는 다중상속을 지원하기 위해 Interface가 생겨났다고 하지만, 그것은 고전적 C++ 프로그래머가 생각하는 Interface가 가진 하나의 결과적(derived)속성에 불과할 뿐입니다.

하지만, C++ 은 이러한 interface와 같은 확고한 개념이 생겨나기 전에 등장한 언어였고, 꽤나 미심쩍은 형태인 Abstract Class가 이 일을 대신합니다. 물론 Abstract Class는 인터페이스의 대안으로 사용할 수 있을 뿐만 아니라 멤버 필드도 가질 수 있습니다. 다중 역할 획득 역시 다중 상속이 가능하기 때문에 해결이 됩니다. (물론 상속체계는 난잡해 지지만)

그 이유는 Abstract Class의 기능적 도메인이 Interface를 포함하고 있기 때문입니다. (개념적 도메인은 오히려 더 좁습니다. 많은 것을 제공할수록 적용 분야는 좁아지고, 특화 되지요)

Interface:
역할을 획득하기 위해 반드시 인식할수 있는 메시지들을 명시한다.
→ 제시된 조건을 충족하면 그러한 역할을 수행하는 객체로 볼 수 있다.

Abstract Class:
역할들을 획득하기 위해 반드시 인식할 수 있는 메시지들을 명시한다.
어떠한 메시지의 처리 기법들은 공통적일 수도 있다.
이 역할을 수행하는데에 필요한 일부 멤버 필드들은 동일 할 수도 있다.
→ 제시된 조건중 그 역할 수행기법이 명시되지 않은 메시지에 대하여 상속받은 객체는 반드시 특화된 자기 자신의 역할 수행 방법을 기술해야 한다.

따라서 Abstract Class는 역할 뿐만 아니라, 부분적으로 그 역할 수행 방법까지 제시하고 있습니다. 이것은 단순히 관점만을 보는 것이 아니라, 어떤 역할을 수행함에 있어서 일부분의 방법 자체 역시 제공을 하고 있는 것입니다.

물론 abstract Class는 Interface와 완전히 동일하게 사용할 수도 있습니다. 대신 다른 상속 체계를 선택할수는 없게 되기는 하겠지만요.


2.2 그렇다면 Interface는 왜 abstract class가 가진 기능의 일부를 포기하고도 탄생하게 되었는가?

그렇다면 왜 이러한 특성이 오늘날 필요로 하게 되었는 가를 알아 봅시다. 오늘 날의 소프트웨어는 Jini님이 말씀하신 것처럼 다양한 컴퓨팅 환경 및 믿을 수 없을정도로 다양한 도메인으로 부터, 그 수요가 창출되고 있습니다. 따라서, 각 전문 도메인을 가진 개발팀 구성원간이 커뮤니케이션 방법이 절실하게 요구되었고, 그러한 수요에 따라 등장한 것이 UML과 같은 랭귀지 입니다.

객체 도메인 모델링의 기초는, 일정한 역할을 수행하는 객체들을 같은 공간에 두어 서로 협력하게 하여 결론을 이끌어 낸다는 방식입니다. 이 컴포넌트(객체)들은 각자 인캡슐레이션 되어있으며, 최소한 서로 의사소통하는데에 필요한 관점(Connector)만을 제공 받게 됩니다. 실제 어떤 학생에 대한 정보가 있다 하더라도, 성적 처리 시스템은 그 학생의 취미나 취향에는 관심도 없을 뿐더러, 그러한 관점을 사용하게 되면 overload가 발생합니다. 그래서 학생이라는 관점(interface)로 그 객체를 상대하게 됩니다. 하지만 그 학생은 다른 시스템에서 다른 관점으로 참조 될런지도 모릅니다. 따라서, 그 객체는 다중 역할을 획득하고 있어야 재사용성을 얻을 수 있습니다. 이렇게 함으로써, 각각의 컴포넌트들은 서로 상대방이 어떤식으로 구현되어있으며, 또 얼마나 다른 여러 역할을 수행하고 있는가에 대해서는 아무런 관심을 가지지 않아도 되게 되어, 자기자신에게 주어진 역할들에 충실하면서, 나에게 제공하는 연결된 컴포넌트들의 관점에만 충실하면 되게 됩니다.

이러한 상태에서, 어떤 컴포넌트의 어떤 역할 수행방법이 변경된다 하더라도 ChangeEffect는 거의 일어나지 않게 됩니다. 즉 공동개발이 수월해 질 뿐만 아니라 (거의 CVS 시스템이 따로 필요 없을 정도로!!!) 각 단위는 주어진 관점에서 요구하는 문제 해결에만 주력하면 되게 됩니다. 따라서 각 유닛의 품질은 상승할 뿐만 아니라 cost는 줄어 듭니다.

그러나, 이러한 관점사이에 강제하는 어떠한 멤버 필드가 있다면, 원래는 역할만을 제공만 하면 되었던 루즈한 응집성이, 어떻게 그 역할을 추상화 할 것인가에 대해 어느정도 강제성을 가지게 되어 버리고 맙니다. 왜냐하면 멤버 필드는 단순히 가시성 뿐만이 아니라 그 값에 따른 sematic까지 가질 수 밖에 없기 때문입니다. 따라서 해당 역할에 대한 구조적 이해가 모든 구성원에게 있어야 할 뿐만 아니라, 이러한 관점에서 멤버필드가 수정될 시에, 이를 통해 연결되어있던 컴포넌트는 구조자체가 완전히 흔들려 버리게 되고 맙니다. 결국, 그러한 판단이 서게 될 경우에는, "재설계"에 들어가게 되는 것입니다. 물론 재설계하기 싫다면, interface 대신에 abstract class를 사용하여, 그것이 가능하게끔 할수도 있습니다. 하지만 프로젝트가 진행 될 수록, 이것이 미치는 오차 범위는 걷잡을 수 없을 정도로 확대 될 것이고, 이 시스템 기반위에서 동작할 상위 아키텍처는 거의 예측이 불가능한 오차 확산에 노출 될 것입니다. 거기에 유연성, 확장성, 유지 보수성은 완전히 포기해야 하며 매우 복잡한 CVS알고리즘이 필요해 질 것입니다.

따라서 아무것도 강제하지 않되, 명확한 관점을 제공할 방법이 필요해 지게 된 것 입니다. 그것이 인터페이스 이며, 멤버필드를 포함하지 않는 이유 입니다.


3. 인터페이스 만으로도 부족하다, 인터페이스 조차 강제적 구현을 요구하지 않느냐? 이제는 묵시적 인보킹 시스템 시대이다.

3.1 인터페이스도 부족하다.

그러나 인터페이스라고 해도 그것 자체의 변경이 없다고 assertion할 수는 없습니다. 그러한 경우는 Java에서도 볼 수 있습니다. 디프리케이트 되는 경우가 바로 그러한 예지요. 하지만 이 경우 멤버 필드의 변화만큼 커다란 파급 효과를 가져 오지는 않습니다. 단지 하나나 둘 정도의 메소드 네임이 바뀌거나 바디가 수정되거나, 단지 deprecated 라고 써주면 그만이지요. 물론 멤버 필드가 변경될 경우 구조가 뒤흔들리는 것에 비하면 아주 적은 파급 효과입니다.

그러나 오늘날처럼 복잡한 시스템은 더욱 뛰어난 유연성을 요구하게 됩니다. 그래서 등장한 것이 implict Invocation (Independent Component System)입니다. 인터페이스를 사용한다 하더라도 어떤 한 컴포넌트가 다른 컴포넌트에게 메시지를 전달하려면 인터페이스가 명시한 어떠한 메시지 형태를 따라야만 합니다. 따라서 이 둘 컴포넌트 사이에는 완전한 독립성이 제공되지 못합니다. 즉 어떤 컴포넌트가 어떤 컴포넌트를 활성화 하려면 그 실체에대해서 알지는 못하더라도, 그 역할이 어떻게 규정되어있는가 하는 것은 미리 알고 있어야만 한다는 것입니다.

그래서 등장한 것이 바로 Message-Oriented System입니다. 혹은 Event-driven System이라고도 하지요.

3. 2 Message-Oriented System

예를 들어 JButton에서 액션 이벤트가 발생하면, 그것을 다른 객체가 처리해 줄수도 있습니다. 단지 이 객체에게 요구되는 것은 ActionListener라는 역할을 수행할 수 있는 능력을 가지고 있어라 하는 것 뿐입니다. 또 실제 버튼이 눌릴 때 마다, 프로그래머는 명시적으로 actionPerformed()를 호출해 줄 필요가 없습니다. 이벤트 모델에 의하여 묵시적으로 호출이 이루어 지게 되지요. 두 컴포넌트 사이에는 어떠한 제약도 존재하지 않을 뿐더러, 심지어는 어떤 객체이던지 ActionEvent를 마음껏 발생시킬 수 있습니다.

이제 두 객체사이에 의미를 가진 메시지를 전달하면서도, 아무런 응집성이 존재하지 않게 된 것입니다. (물론 실제로는 프로시져가 호출을 하지만, 상위 추상 레벨에서는 그렇지 않다고 생각하도 무방할 정도로 잘 일반화가 되어있는 것입니다. 이것은 실제적으로 자바가 어떻게 동작하느냐와는 상관이 없습니다. 왜냐하면 이것이 제공하는 sematic layer가 그리하기 때문입니다. 실제로 리눅스의 JVM과 Windows의 JVM은 완전히 다르지만 똑 같은 의미 레이어를 제공하지요. 그것이 가능한 것이 바로 인터페이스의 위력입니다.)


4. 결론
매우 저차원 적인 메시지 전달 방법 과 저수준의 객체간의 통신 수단 및 컴퓨터에만 특화된 이벤트에 대한 개념을 제시하는 C++은 이벤트라고 해봐야 mousePressed 같은게 아니겠느냐 하는 지나치게 컴퓨터적이며 랭귀지적인 사고를 강요하기 쉽습니다.

하지만 자바에서는 buttonPressed가 아니라 actionPerformed라는 용어를 사용하고 있습니다. 이것은, 자바에서의 Event란 환자가 혼수상태에 빠졌다거나, 매물거래가 성행한다거나, 누군가가 죽었다거나 하는 매우 보편적인, 즉 일반화된 이벤트를 추상화하고 있음을 암시하는 대목입니다. 리얼 월드의 문제점을 쉽게 기술하기 위해서는 더욱더 일반화된 개념을 이용하여 구조를 설계하고 개발해야만 합니다. (그럼에도 불구하고 Java의 Event는 modifier같은 컴퓨터적 플래그가 존재합니다. 하지만 이것은 java.awt.*에 속해 있으므로 문제가 되지는 않습니다. 패키지를 통하여 개념을 흩트리지 않고도, 개발편의를 제공할 수 있다는 것이지요)

플랫폼으로의 전이나 최적화는 단지 최종 임플리멘테이션 노가다일 뿐입니다. 만약 그러한 것에 얽매여, 개념적 명확성화 일반화를 포기한다면, 그것은 임플레멘테이션 트랩에 빠지게 되는 것이며, 원래 얻고자 하는 해와는 거리가 먼 곳에서 시간을 낭비하게 될 가능성이 많습니다.


ps. 개발자의 요구와 컴퓨팅 환경에 따라 변화하는 것은 시스템의 구조이어야 하지 역할규명자가 될 수 없습니다. 만약 그렇게 했다면 그것은 객체가 활동하는 역할 도메인을 재정의 하는 것으로, refactoring이나 optimizing에 가까운 최종 임플리멘테이션 테크닉일 뿐으로서, 이미 개발의 영역과 모델링, 추상화가 배제된 deploy 직전의 low-level코드일 뿐입니다. 그것은 최종 전이 단계 직전에 최적화 팀이나, 최적화 툴들이 해야 할 일입니다. 결코 개발자가 해서는 안될 일입니다. 더더군다나 Jini님이 말씀하시는 대로 유동적으로 변화해서 획득된 것이 오늘날의 Java의 Interface입니다.

랭귀지는 도구일 뿐인 하찮은 존재이며, 그 방대함과 유연성은 우리에게 아무런 의미도 없습니다. -어셈블리어가 그러하듯이- ㄱ, ㄴ, ㄷ의 특성과 그것의 조합의 수가 3만개가 넘는 다는 것과는 아무 상관 없이 소설은 씌여집니다. (비록 50개 발음 밖에 없는 일본이라 할지라도) 그러나 자바는 단순히 새로운 랭귀지가 아닌, 위에서 말한 새로운 개념들을 포함하여 새로운 구조적 학문적 기법이 수없이 많이 적용된 새로운 메소돌로지입니다.

어째서 단지 interface에 멤버 필드가 없느냐라고 의문을 가진것에 대해 이렇게까지 격렬한 반응을 가지는지 의문을 가지시고 부디 이것에 대해 연구해 보기를 원합니다.

Java를 단지 새로운 언어로 인식한다면, 굳이 오히려 퍼포먼스와 기능성이 제한된 Java를 사용해야 할 이유가 없습니다.
Posted by la30321
Java2005. 3. 22. 10:36

Concurrency Utilities

Doug Lea JSR-166 의해서 진행된 Concurrency 유틸리티 패키지는 J2SE 1.5 특별히 추가 되었습니다. 강력한 상위 레벨 스레드 생성자와 실행자를 포함합니다. 스레드 타스크 프레임워크, 스레드 세이프 , 타이머, , 다른 동기화 변수를 사용할 있습니다.
세마포어로 잘 알려진 락은 wait과 비슷하게 코드의 접근을 막을 수 있습니다. 세마포어는 유연합니다. 여러 개의 스레드를 억세스하고 락을 걸 수 있습니다. 다음 예제에서는 단 하나의 세마포어를 사용하고 있습니다. java.util.concurrent 패키지에서 더 많은 정보를 얻을 수 있습니다.
final private Semaphore s= new Semaphore(1, true); s.acquireUninterruptibly(); //for non-blocking version use s.acquire() balance=balance+10; //protected value s.release(); //return semaphore token


Jini Say

자바에서 스레드는 블랙박스입니다. 예측이 불가능하고 구현도 어렵습니다. 가장 큰 문제중 하나인 동기화와 억세스 컨트롤이 해결될 것으로 보입니다.


2004-05-20 23:44:07.0 (papilla 220.122.230.33) D

물론 Java의 Thread는 블랙박스입니다. (그렇지 않은 스레드나 멀티 프로세스도 있나요?)

Java의 경우 Thread를 상속받아 아주 자유롭게 디자인 하는 것이 가능하기 때문에,
Thread를 상속받아 내부에 progress나 스레드의 상태를 represent할 수 있는 구조로 재 확장 하여 사용하면, 쉽게 예측할수도 있을 뿐더러 대부분의 동기화 문제를 별도의 도구 없이 해결 가능합니다.
뭐든지 자기 입맛대로 상속받아 만들어 쓸 수 있다는 것이 자바의 가장 큰 매력이지요.

단순히 세마포라면 싱크로나이즈드 만으로도 충분하지요. 세마포가 제공하는 정보는 오늘 날의 복잡한 스레드 사이의 동기화 조율을 하기에는 너무 적은 정보일 뿐입니다.

Posted by la30321
Java2005. 3. 22. 10:34

Varargs

Varargs 기능은 여러 개의 가변적인 아규먼트를 메소드로 전달하는 것을 허용합니다.
void argtest(Object ... args) { for (int i=0;i <args.length; i++) { } } argtest("test", "data");


Jini Say
Varargs 는 JCP 아저씨들이 만든 신조어 입니다. Variable Arguments 정도가 되겠네요. 상당히 유용합니다. 그 동안 이 짓을 하기 위해서 배열이나 Linked List를 사용했습니다. 보기에 좋지도 않고 번거롭고 좋기는 하지만, 타입체크가 문제가 되겠네요.

2004-04-22 14:44:22.0 (투바 61.40.236.142) D

varargs 는 JCP 에서 처음 나온 단어가 아닐 겝니다.
C 에서는 이미 오래전부터 저런 매크로가 있었습니다.
예를 들면,

printf( "%s %d %f \n", a, b, c );

라는 형식으로 함수를 만들려면, 인자를 가변적으로 처리해야 되거든여.
varargs 라는 매크로가 있답니다.

그냥 잡담이네여.

2004-05-20 23:54:10.0 (papilla 220.122.230.33) D

이번 1.5에서 Generic Type이 지원되지요.

예를 들어 Vector<Integer>로 만들면 Vector에서 꺼낼때 자동으로 오토박싱을 해 주어서 Integer로 바로 꺼낼수 있게 되지요. 그 말은, 단순히

Class에 대한 추상화를 제공하는 것이 아니라, casting까지 가능한 수준으로, Type에 대한 추상화가가능해 진 것입니다. java1.5의 class.java를 보시면 왜 이러한 것들이 가능해 졌는지 아실 수 있으실 겁니다. Type에 대한 추상화 존재 없이는, 리턴형이 고정될 수 밖에 없습니다.

결국 type에 대한 추상화가 가능해짐으로 인해, varargs가 가능해 졌고요, 이것이 가능해지자, 오랜 고전 프로그래머의 숙원이던, printf가 생겨났습니다.

하지만 개인적으로 이것에 대해 아주 부정적으로 생각합니다. printf가 없다 하더라도 자바에는 훨씬더 강력한 Formatting기능이 있습니다. printf는 출력과, 포멧팅이 합쳐진 형태이기 때문에, 이 둘 사이의 응집성이 지나치게 높아, 한번 출력하고자 만들어진 데이터는 다른데 써먹을 수가 없습니다.

자바는 기본적으로 포멧팅과 입출력은 분리되어있습니다. 자칫 멀쩡한 스트림에 어댑터를 덕지 덕지 붙여 사용하는 것이 불편해 보일 수도 있지만, 이것은 매우 강력한 활용성을 보여줍니다.

예를 들어toString()과 paint(Graphics g)처럼 표현영역에 따른 포멧팅이 출력 자체와 분리되어있다면, 컴포넌트는 어디에서든 표현이 가능해 지지요.

이번 자바1.5는 개념의 명확성을 좀 잃더라도, 개발자들의 편의에 맞추어 굴복(?) 한 것 처럼 보여, 다소 아쉬움이 많이 남습니다. Class.java같은 코어의 내용이 변경되었다는 것은 어떻게 보면 비극입니다

Posted by la30321