#14장 자바 모듈 시스템
이 장의 내용
자바가 진화해야 한다는 여론으로 자바가 모듈 시스템을 지원하기 시작함
주요구조 : 모듈 declarations, requires, exports 지시어
기존자바 아카이브(JAR)에 적용되는 자동 모듈
모듈화와 JDK 라이브러리
모듈과 메이븐 빌드
기본적인 requires, exports 외의 모듈 지시어 간단 요약
들어가기
자바 9에서 가장 많이 거론되는 새로운 기능은 바로 모듈 시스템 (완성까지 거의 10년 걸림)
모듈 시스템 책 추천 : Nicolai Pariog 저 The Java Module System
압력 : 소프트웨어 유추
앞선 장들에서 유지보수 하기 쉬운 코드를 위한 언어 기능들을 소개했지만, 저수준의 영역에 해당한다. 궁극적으로 소프트웨어 고수준(e.g.,아키텍처)에서는 기반 코드를 바꿔야 할 때 유추하기 쉬우므로 생산성을 높일 수 있는 소프트웨어 프로젝트가 필요하다.
관심사 분리 (SoC)
회계 시스템 개발 한다고 가정하면, 파싱, 분석, 레포트 기능을 모듈이라는 겹치지 않는 코드 그룹으로 분리할 수 있다.
SoC 원칙은 M, V, C 같은 아키텍처 관점 그리고 복구 기법을 비즈니스 로직과 분리 하는 등의 하위 수준 접근 등의 상황에 유용하다.
장점 요약
개별 기능에 따른 팀간의 협업
재사용성
유지보수성
정보 은닉
Encapsulation = 내부적인 변화가 의도치 않게 외 부에 영향을 미칠 가능성을 줄인다
자바에서는 클래스 내의 컴포넌트에 private 키워드를 사용했는지를 기준으로, 컴파일러를 이용해 캡슐화를 확인할 수 있다. 하지만 자바 9 이전까지는 클래스와 패키지가 의도된 대로 공개되었는지를 컴파일러로 확인할 수 있는 기능이 없었다.
자바 소프트웨어
전체 코드를 보고 SW 동작 추론은 어렵다. UML 같은 것을 사용해야 하는데, 모듈화는 그룹 코드 간의 의존성을 시각적으로 보여줄 수 있다.
자바 모듈 시스템을 설계한 이유
자바 9 이전 모듈화의 한계
클래스, 패키지, JAR 세 가지 수준의 코드 그룹화를 제공했다. 클래스 수준에서 제한자와 캡슐화를 지원했지만, pacakage와 JAR 수준에서는 지원하지 않았다.
제한된 가시성 제어
한 패키지의 클래스와 인터페이스를 다른 패키지로 공개하려면 public으로 이들을 선언해야 한다. 결과적으로 이들 클래스와 인터페이스는 모두에게 공개된다.
클래스 경로
클래스들을 컴파일한 다음 보통 한 개의 평범한 JAR 파일에 넣고 클래스 경로에 이 JAR 파일을 추가해 사용한다.
클래스 경로와 JAR 조합 단점
클래스 경로에는 같은 클래스를 구분하는 버전 개념이 없다.
이로써, 두 가지 버전의 같은 라이브러리가 존재할 때 어떤 일이 일어날지 예측할 수 없다.
클래스 경로는 명시적인 의존성을 지원하지 않는다 (aka, AR 지옥 or 클래스 경로 지옥) 즉, 한 JAR가 다른 JAR에 포함된 클래스 집합을 사용라고 명시적으로 의존성을 정의하는 기능을 제공하지 않는다.
거대한 JDK
JDK가 거대해지다보니 사용하지 않는 CORBA 클래스도 포함하게 되었다. 모듈을 사용하면, JDK에서 필요한 부분만 골라 사용하고 (compact profile), 클래스 경로를 쉽게 유추할 수 있으며, 플랫 폼을 진화시킬 수 있는 강력한 캡슐화를 제공할 새로운 건축 구조를 제공한다.
OSGi: 자바 9 이전 모듈화 애플리케이션을 구현하는 표준. OSGi 내의 모듈들을 번들이라 부름.
자바 모듈 : 큰 그림
모듈 = 구조 단위
module descript는 module-info.java 에 저장되고, 한 개 이상의 패키지를 서술하고 캡슐화할 수 있지만 단순한 상황에서는 이들 패키지 중 한 개만 외부로 노출시킨다.
module-info.java에는 module(모둘명), exports (내보낼 패키지), require(필요한 모듈)가 있다.
자바 모듈 시스템으로 애플리케이션 개발하기
애플리케이션 요구사항과 모듈화
비용 관리 시스템을 만들어보자.
요구사항
파일이나 URL에서 비용 목록을 읽는다.
비용의 문자열 표현을 파싱한다.
통계를 계산한다.
유용한 요약 정보를 표시한다.
각 태스크의 시작 마무리 지점을 제공한다
여러 클래스와 인터페이스를 정의해야 한다.
먼저 Reader 인터페이스는 소스에서 얻어온 직렬화된 지출을 읽는 역할을 한다. 소스가 어디냐에 따라 HttpReader, FileReader 등 여러 구현을 제공해야 한다. 또한 JSON 객체를 사용할 수 있는 Expense 객체로 재구성할 Parser 인터페이스도 필요하다. 마지막으로 주어진 Expense 객체 목록으로 통계를 계산하고 SummaryStatistics 객체를 반환하는 SummaryCalculator 클래스가 필요하다.
이 기능들을 어떻게 모듈화 할 수 있을까? 우선, 관심사에 따라 분리해보자.
다양한 소스에서 데이터를 읽음
(Reader, HttpReader, FileReader)
다양한 포맷으로 구성된 데이터를 파싱
(Parser, JSONParser, ExpenseJSON - Parser)
도메인 객체를 구체화
(Expense)
통계를 계산하고 반환
(SummaryCalculator, SummaryStatistics)
다양한 기능을 분리 조정
(ExpensesApplication)
결과적으로 다음과 같은 모듈이 나온다.
expenses.readers
expenses.readers.http
expenses.readers.file
expenses.parsers
expenses.parsers.json
expenses.model
expenses.statistics
expenses.application
모듈 크기에 따른 세부적인 모듈화와 거친 모듈화
거친 모듈화 = 한 모듈이 시스템의 모든 패키지 포함
여러 모듈 활용하기
모듈간의 상호 작용은 export, requires를 이용해 이루어진다.
exports 구문
requires 구분
사례를 위해, java.base 모듈을 가져와 봤다. (실세계에서는 java.base 대신 외부 모듈과 라이브러리를 한다.) java.base에서 외부로 노출한 공개 형식을 reader 모듈에서 사용할 수 있다.
모듈의 이름 규칙
인터넷 도메인명을 역순 권고 (e.g, com.iteratrlearning.training)
컴파일과 패키징
각 모듈에 pom.xml를 추가한다. 부모 모듈에도 pom.xml을 추가한다. 구조는 다음과 같을 것이다.
expenses.readers 프로젝트의 pom.xml의 경우 다음과 같다.
expenses.application 프로젝트의 pom.xml의 경우 다음과 같다.
전역 pom.xml을 설정해보자.
target 폴더에, 다음 artefact가 만들어진다.
위 두 JAR를 모듈 경로에 포함해서 실행할 경우 다음과 같다.
자동 모듈
reader 모듈에서 아파치의 httpclient 같이 외부의 특화 라이브러리를 사용하려 할 경우 어떻게 할까?
expenses.readers 프로젝트의 module-info.java에 requires을 추가한다. 그리고 나서 아래처럼 의존성을 추가한다.
httpclient는 자바 모듈로 사용하려는 외부 라이브러리인데 모듈화가 되어 있지 않은 라이브러리다. 자바는 JAR를 자동 모듈이라는 형태로 적절하게 변환 한다. 모듈 경로상에 있으나 module-info 파일을 가지지 않은 모든 JAR는 자동 모듈이 된다.
자동 모듈의 이름을 바꾸기 위해서는, --describe-module
모듈 정의와 구문들
requires, exports 구문을 배웠지만, 이 외에 requires-transitive, exports-to, open, opens, uses, provides 같은 다른 구문들도 있다.
그렇지만, 모듈 선언문은 대부분 단순한 exports와 requires로 구성되어 있다.
requires transitive
ui에서 core를 transitive로 해둬, application에서도 재선언할 필요 없이 core를 사용할 수 있다.
exports to
사용자에게 공개할 기능을 제한함으로 정교한 제어가 가능하다. widgets에 대한 접근을 widgetuser로 제한한다.
open과 opens
모든 패키지를 다른 모듈에 리플렉션 접근을 허용할 수 있다.
uses와 provides
고급 주제이므로 스킵.
더 큰 예제 그리고 더 배울 수 있는 방법
모듈 선언문은 대부분 단순한 exports와 requires로 구성되어 있다.
Last updated