package에 대해 공부하던 중, module이라는 개념을 새롭게 접하게 되었습니다. module이 무엇인지, 언제 생겨났는지, 언제 쓰이는지에 대해 공부해보았습니다.
Index
- JPMS(Java 9 Platform Module System)
- JPMS 이전
- JPMS의 목표
- Module
- Module Example
- Module 선언하기
- Module Keywords
1. JPMS(Java 9 Platform Module System)
Oracle Java 9에서 JPMS와 함께 Module이 소개되었습니다.
Module은 OpenJDK의 Jigsaw 프로젝트의 결과물입니다. Jigsaw 프로젝트에 대한 자세한 내용은 아래의 링크에서 참고할 수 있습니다.
Project Jigsaw
Project Jigsaw The primary goals of this Project were to: Make it easier for developers to construct and maintain libraries and large applications; Improve the security and maintainability of Java SE Platform Implementations in general, and the JDK in par
openjdk.java.net
모듈 방식의 장점부터 이야기해보자면, 개발자가 소프트웨어 시스템(특히 대규모 시스템)을 개발, 유지 보수, 그리고 발전시킬 때 생산성을 높일 수 있습니다.
2. JPMS 이전
Java SE 플랫폼은 1995년부터 존재해왔습니다. 개발자들은 IoT나 임베디드 디바이스 등을 위한 작은 애플리케이션부터 비즈니스를 위한 대규모의 시스템까지의 다양한 시스템을 개발하기 위해 Java SE를 사용해왔습니다.
이때까지 Java 플랫폼은 하나의 시스템이 전체를 이루는 모놀리틱 솔루션으로 제공되었고, Open JDK 및 Oracle은 Java SE를 모듈화하기 위해 오랜 시간동안 노력했습니다.
그러다 2005년에 JSR 277: Java Module System이 최초로 Java 7을 대상으로 소개되었습니다. 그리고 나중에 Java 8을 대상으로 하는 JSR 376: Java Platform Module System으로 대체되었습니다. 그리고 Java 9에서부터 정식으로 모듈 시스템이 도입되었습니다.
3. JPMS의 목표
Java에서 JPMS를 통해 이루고자 하는 목표는 다음과 같습니다. JPMS의 목표를 이해하기 위해서는 모듈이 무엇인지 알아야 합니다. 뒤에서 더 자세하게 다루고, 지금은 '패키지의 묶음' 정도로 이해하시면 될 것 같습니다.
1. Reliable configuration(신뢰적인 구성)
모듈은 서로 의존 관계를 가질 수 있는데, 모듈 간의 의존성을 명시적으로 선언할 수 있는 매커니즘을 제공합니다. 이 매커니즘을 통해 모든 모듈 중에서 시스템에 필요한 모듈만을 고를 수 있습니다. 설정된 의존성은 컴파일 시와 런타임 시에 모두 인식됩니다.
2. Strong encapsulation(강력한 캡슐화)
모듈 내에 있는 패키지를 다른 모듈이 접근할 수 없습니다. 모듈 configuration에서 명시적으로 export한 패키지에 대해서만 다른 모듈이 접근할 수 있습니다. 그리고 다른 모듈은 그 패키지를 명시적으로 사용한다고 선언해야 사용할 수 있습니다.
모듈의 캡슐화는 잠재적인 공격자가 클래스에 접근하는 것을 막아주기 때문에 플랫폼의 보안을 향상시킵니다.
3. Scalable Java platform(확장 가능한 Java 플랫폼)
이전에는 Java 플랫폼이 방대한 패키지들로 구성된 모놀리틱 플랫폼이었기 때문에, 시스템을 개발, 유지 보수, 발전시키는 데에 어려움이 있었습니다. 이제는 플랫폼이 여러 모듈로 모듈화되어 애플리케이션에 필요한 모듈들로만 런타임을 구성할 수 있습니다. 이를 통해 런타임의 크기를 줄일 수 있습니다.
4. Greater platform integrity(더 나은 플랫폼 무결성)
Java 9 이전에는 Java API 중에 사용하라는 의미가 아니었던 클래스들을 애플리케이션에서 사용할 수 있었습니다. 이제는 캡슐화를 통해 이런 내부 API들은 Java 플랫폼을 사용하는 애플리케이션에서는 숨겨지고 사용할 수 없습니다.
5. Improved performance(향상된 성능)
JVM은 애플리케이션의 성능 향상을 위해 다양한 최적화 기법을 사용합니다. 이런 기법들은 모듈 간의 의존성을 컴파일 단계에 미리 알고있을 때 더 효과적입니다.
4. Module
그렇다면 모듈이란 무엇일까요? 모듈은 패키지의 상위 집합체입니다.
모듈은 고유한 이름을 가지고, 서로 관련있는 패키지들, 리소스(이미지, XML 파일 등), module descriptor로 구성됩니다.
모듈의 특징
- 고유한 모듈명을 가진다.
- 모듈간 의존성을 가진다.
- 모듈간 캡슐화가 적용된다.
- service provider가 될 수 있다.
- service consumer가 될 수 있다.
5. Module Example
Java 11 모듈
Oracle JDK 11의 Contents/Home 디렉토리를 보면 다음과 같이 java. 와 jdk. 로 시작하는 수많은 모듈들이 있습니다. (너무 많아서 공백으로 구분지었습니다.)
java.base java.compiler java.datatransfer java.desktop java.instrument java.logging java.management java.management.rmi java.naming java.net.http java.prefs java.rmi java.scripting java.se java.security.jgss java.security.sasl java.smartcardio java.sql java.sql.rowset java.transaction.xa java.xml java.xml.crypto jdk.accessibility jdk.aot jdk.attach jdk.charsets jdk.compiler jdk.crypto.cryptoki jdk.crypto.ec jdk.dynalink jdk.editpad jdk.hotspot.agent jdk.httpserver jdk.internal.ed jdk.internal.jvmstat jdk.internal.le jdk.internal.opt jdk.internal.vm.ci jdk.internal.vm.compiler jdk.internal.vm.compiler.management jdk.jartool jdk.javadoc jdk.jcmd jdk.jconsole jdk.jdeps jdk.jdi jdk.jdwp.agent jdk.jfr jdk.jlink jdk.jshell jdk.jsobject jdk.jstatd jdk.localedata jdk.management jdk.management.agent jdk.management.jfr jdk.naming.dns jdk.naming.ldap jdk.naming.rmi jdk.net jdk.pack jdk.rmic jdk.scripting.nashorn jdk.scripting.nashorn.shell jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom jdk.zipfs
java.base 모듈
위의 수많은 모듈들 중에 제일 중요한 모듈은 java.base 모듈입니다. 저와 같은 학생 개발자들이 흔히 아는 java.lang, java.util 등의 패키지들이 이 java.base 모듈에 있습니다. 오라클 Docs의 설명에 따르면 java.base 모듈은 Java SE 플랫폼의 기본 API들을 정의한 모듈이라고 합니다.
위에서 모듈들은 서로 의존 관계를 가질 수 있다고 했습니다. java.base 모듈은 기본적인 API들이 있는 모듈인 만큼 다른 모듈에 의존하지 않고, 다른 모듈들은 아래의 그래프와 같이 명시적 또는 암묵적으로 java.base 모듈에 의존합니다. 명시적으로 의존한다는 것은 module configuration 파일에 다른 모듈을 의존한다고 명시했다는 뜻이고, 암묵적으로 의존한다는 것은 의존하는 모듈이 의존하는 모듈을 직접 명시하지 않아도 자동으로 의존되게끔 설정되었을 경우를 말합니다.
위에서 모듈은 패키지, 리소스, module descriptor로 구성되었다고 했습니다. java.base 모듈은 아래 사진과 같이 7개의 패키지들과 module-info.class 라는 파일의 module descriptor로 구성되어 있습니다. module-info.class 파일에 모듈이 선언되어 있습니다.
6. Module 선언하기
module descriptor는 모듈의 정보를 담는 메타데이터로, 모듈을 정의하고 모듈의 의존성과 내보내거나 사용하는 패키지들, 제공하거나 소비하는 서비스들을 명시하는 곳입니다. module-info.java 라는 이름의 파일을 컴파일한 module-info.class 파일이 바로 module descriptor입니다. 이 module descriptor는 최상위 폴더에 위치합니다.
module-info.java 파일에 module 키워드를 사용하여 모듈을 선언할 수 있습니다.
module modulename {
...
}
body 부분은 비워도 되고, exports, uses 등의 다양한 키워드들로 모듈 의존성, 패키지, 서비스를 명시할 수도 있습니다.
7. Module Keywords
위에서 선언한 module의 body 부분에 스이는 keyword에 대해 알아보겠습니다.
1. requires
각각의 모듈은 의존 관계를 가질 경우 반드시 의존성을 명시해야 합니다. requires 키워드는 이 모듈이 의존하고 있는 다른 모듈을 명시할 때 쓰는 키워드입니다. 모듈 A가 모듈 B를 requires한다면, 모듈 A는 모듈 B를 읽고, 모듈 B는 모듈 A에게 읽혀지는 관계라고 할 수 있습니다.
requires modulename;
requires static 키워드를 통해 optional dependency를 선언할 수 있다. optional depency란, 컴파일 시에는 모듈을 필요로 하지만 런타임 시에는 선택적인 의존성을 말합니다.
requires static modulename;
2. requires transitive
requires transitive 키워드로 의존성을 선언하면, 이 모듈을 의존하는 다른 모듈들도 이 키워드로 선언한 모듈들을 읽을 수 있습니다. 이것을 implied readability라고 합니다. 모듈 A에 requires transitive 키워드로 모듈 B를 선언하면, 모듈 A를 읽는 다른 모듈들도 모듈 B를 암묵적으로 읽을 수 있습니다.
requires transitive modulename;
3. exports, exports-to
exports 키워드는 다른 모듈의 코드에서 접근할 수 있는 public 타입의 패키지들을 지정합니다.
exports packagename;
exports-to 키워드는 정확히 어떤 모듈이 패키지에 접근할 수 있는지 명시할 때 사용합니다. 이것을 qualified export라고 합니다. 접근할 수 있는 모듈들은 콤마로 구분된 리스트로 작성할 수 있습니다.
exports packagename to modulename;
exports packagename to
modulename1,
modulename2,
modulename3;
4. uses
uses 키워드는 이 모듈에서 사용하는 service를 지정합니다. service란 인터페이스를 구현한 클래스, 추상클래스를 확장한 클래스의 객체를 말합니다. uses 키워드는 이 모듈을 service의 consumer로 만드는 것이기도 합니다. uses 키워드로 선언하는 인터페이스나 추상 클래스들은 이 모듈 안에 존재합니다.
uses classpath.interfacename;
uses classpath.abstractclassname;
5. provides-with
provides-with 키워드는 이 모듈이 제공하는 service의 구현체를 지정할 때 사용합니다. 이 모듈을 service의 provider로 만드는 것이기도 합니다.
provides 부분에는 인터페이스나 추상클래스를 선언하고, with 부분에는 이 인터페이스를 구현하는 클래스나 추상클래스를 확장하는 클래스를 선언합니다. provides-with로 선언하는 인터페이스나 추상클래스들은 다른 모듈에 존재합니다.
provides classpath.interfacename with implementation;
provides classpath.abstractclassname with extendedclassname;
6. open, opens, opens-to
Java 9 이전에는 reflection을 사용하여 패키지의 모든 클래스들과 그 멤버에 대해 알 수 있었습니다. 따라서 제대로 캡슐화되지 않았습니다.
모듈 시스템의 핵심 동기는 강력한 캡슐화입니다. 디폴트로 모듈 안의 클래스들은 다른 모듈에서 접근할 수 없습니다. public이고 이 패키지를 명시적으로 exports해야만 접근할 수 있습니다. open, opens, opens-to 키워드로 원하는 패키지만 공개할 수 있습니다. Java 9에서부터는 reflection에도 이 사항이 적용됩니다.
다음은 런타임 시에만 접근할 수 있는 패키지를 지정합니다.
opens packagename
다음은 런타임 시에만 접근할 수 있는 패키지를 특정 모듈에서만 접근할 수 있도록 명시합니다.
opens packagename to comma-separated-list-of-modules
다음은 런타임 시에만 모듈의 모든 패키지들을 접근할 수 있도록 지정합니다.
open module modulename {
// module directives
}
* 아래의 링크를 참고하여 작성되었습니다.
https://www.oracle.com/kr/corporate/features/understanding-java-9-modules.html
'Java' 카테고리의 다른 글
[Java] Object 클래스 (0) | 2021.08.08 |
---|---|
[Java] Oracle Java API 버그 제보하기 (0) | 2021.08.08 |
[Java] JVM 구조 (0) | 2020.10.25 |
[Java] Java로 HTTP GET, POST 통신하기 (1) | 2020.10.22 |
[Java] Java 프로그램이 메모리를 사용하는 방식 (0) | 2020.10.01 |