새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
자바 9 Features 와 기타
오늘 학습은 자바 9의 Features(기능과 변경사항)에 대해 학습합니다.
자바 9 Features
자바 8과 자바 9로 변경되면서 많은 변화가 생겼습니다. 가장 눈에 띄는 단어는 모듈입니다.
모듈은 자바 9에서 도입된 개념으로, 여러 코드가 모인 독립적인 구성 요소를 의미합니다. 비슷한 역할을 하는 코드들을 모아 유지보수성을 높이고, 각 모듈 간의 의존성을 제어하여 더 안전한 개발을 가능하게 합니다. 모듈을 사용하면 코드의 구조를 체계적으로 관리할 수 있으며, 필요에 따라 모듈 단위로 분리하고 통합할 수 있습니다.
생각해보면 자바 17을 사용하거나 21을 사용해도 모듈을 별도로 사용한 기억이 없습니다.
그냥 import
를 통해서 사용하다 보면 패키지가 다르네? 정도로 생각했지 모듈 시스템을 사용하고 있다는 걸 명시적으로 확인한 적이 없었습니다.
기존에는 빌드 툴인 Gradle
이나 Maven
을 통해서 프로젝트의 코드를 여러 모듈로 나누고 각 모듈끼리 연관 관계를 설정할 수 있습니다.
//plugins {
// id 'java'
//}
plugins {
id 'java-library'
}
group = 'tobyspring'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
// implementation(project(":con-domain"))
api(project(":con-domain"))
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
test {
useJUnitPlatform()
}
-
plugins.id[‘java’] - implementation(project) : 손님 > 커피 가게 > 커피 머신 관계입니다.
// Machine.module // Coffee.module implementation(project(":Machine")) // Consumer.module implementation(project(":Coffee")) // Consumer not existing Machine
커피 가게는 커피 머신을 사용하여 커피를 내립니다.
손님은 커피를 마시기 위해 커피 가게를 의존하지만 커피 머신은 사용할 수 없습니다.
- 손님 -> 커피 가게 , 커피 가게 -> 커피머신 : OK
- 손님 -> 커피머신 : NOPE
-
plugins.id[‘java-library’] - api(project) : 손님 > 무한 리필고기 가게 > 삼겹살관계입니다.
//Beef.module //Store.module api(project(":Beef")) //Consumer.module api(project(":Store"))
무한 고기 리필집은 삼겹살을 의존합니다.
손님은 삼겹살을 먹고싶어 무한 고기 리필집을 갑니다.
손님은 무한 리필고기집에 가서 삼겹살을 직접 가져와서 구울 수 있습니다.
JPMS vs Gradle module
JPMS는 자바 플랫폼 모듈 시스템이라고 하며 빌드 툴의 모듈 방식과 어떤 차이가 있는지 알아야 합니다.
먼저 JPMS을 사용하는 목적은 패키지보다 상위 단계의 집합(aggragtion
)을 추가할 수 있습니다. Java의 추가된 신규 핵심 언어 요소로 상호 관련성이 높은, 재사용 가능한 패키지 및 리소스(이미지,XML 파일 등)들을 한데 묶어서 지칭하는 명칭입니다.
모듈 디스크립터(module descriptor
)에는 다음과 같은 정보가 담겨있습니다.
- JPMS을 사용하면 빌드 툴 없이 모듈 구성이 가능하다.
처음에 어렵게 생각했습니다. 다시 생각해보면 상호 관련성이 높은 재사용 가능한 집합이라 생각하면 됩니다. JPMS의 “모듈” 설정과 빌드툴 “모듈” 설정을 함께 사용할 수 있습니다. - 더 세밀한 접근 제어가 가능합니다.
강력한 캡슐화가 가능해집니다. 모듈 내의 패키지는 모듈이 명시적으로export
로 내보내는 경우에만 다른 모듈에서 사용이 가능해집니다.
예를 들어, store 모듈에는 org.store.order 패키지만 노출하여 주문만 가능하도록 설정할 수 있고 내부 DTO나 메서드에 접근할 수 없습니다. private 멤버에 대한 리플렉션 오픈 여부도 결정할 수 있습니다.
gradle 멀티 모듈 프로젝트에 JPMS를 적용하기
모듈이 위치한 src 메인 자바 폴더 안에 module.info.java
파일을 생성합니다. 이 파일은 모듈의 이름, 의존성, 공개 패키지 등을 정의하며, 모듈의 경계를 명확히 하고 모듈 간의 관계를 관리하는 데 사용됩니다.
패키지 이름처럼 사용할 수 있습니다.
module com.machine {
exports com.example.machine;
}
module com.store {
requires transitive com.machine;
}
module com.concumer {
requires com.store;
}
위 예시를 보면
- machine 모듈이 있습니다
- store모듈은 machine 모듈을 의존합니다
- concumer 모듈은 store 모듈을 의존합니다.
requires module-name
:
module-name
모듈에 컴파일과 런타임 모두 접근할 수 있습니다.- 현재 모듈은
module-name
모듈에 의존합니다. module-name
모듈의 모든 public API를 사용할 수 있습니다.
requires static module-name
:
module-name
모듈에 컴파일 시에만 접근할 수 있습니다.- 런타임 시에는
module-name
모듈이 필요하지 않습니다. - 주로 선택적 의존성이나 테스트 의존성에 사용됩니다.
requires transitive module-name
:
module-name
모듈에 컴파일과 런타임 모두 접근할 수 있습니다.- 현재 모듈뿐만 아니라 현재 모듈을 의존하는 다른 모듈들도
module-name
모듈에 접근할 수 있습니다. - 즉, 현재 모듈이 의존하는 모듈의 의존성을 다른 모듈로 전달합니다.
여기서 말하는 컴파일 시점은 코드 상의 정의된 메소드를 사용할 수 있다는 의미입니다
런타임 시점은 리플렉션 처럼 런타임 시점에 접근할 수 있다는 의미를 말합니다.
transitive
이거에 대해서 잘 이해가 안갔는데 api(project(": xxx"))
와 동일합니다.
store
모듈을 의존하는 경우 store
모듈은 자신이 의존하는 machine
모듈까지 접근 정보를 제공하겠다는 의미입니다.
이런 경우를 추이적인 의존성(transitive dependency) 라고합니다.
사전에 검색해보니 transitive
옮아가는, 이행하는 이라는 의미로 의존성이 이행된다 는 의미로 받아드리면 될거같습니다.
module event {
requires transitive 제세공과금
}
이벤트에 당첨되셨는데 제세공과금도 같이 이행됩니다 ! 라는 의미입니다.
build.gradle
추가로 module-info.java
파일에 export
를 하고 requires
을 해도 접근이 안됩니다.
build.gradle에 추가해야합니다. 수류탄 모듈을 사용하려면 안전핀 2개를 제거해야하는 것처럼 말이죠
호환성
module-info.java
파일이 없는 경우를 unnamed module이라고 합니다.module-info.java
파일이 있는 경우를 named module이라고 합니다.
자바는 호환성을 중요하게 생각하는 프로그래밍 언어로 모듈 시스템은 내부에 들어왔지만, 모듈 기능을 사용하지 않으면 module-info.java
파일이 없으면 기존 처럼 access modifier
로만 접근 제어가 가능하게됩니다.
module-info.java
를 src/main/java
내부에 저장할 경우 모듈은 강력한 캡슐화 기능을 얻습니다.
댓글남기기