✏️[모던 자바 인 액션, 전문가를 위한 자바 8, 9, 10 기법 가이드] 스터디 관련 책 내용을 정리한 글입니다.
📌 이 장의 내용
- 자바가 진화해야 한다는 여론으로 자바가 모듈 시스템을 지원하기 시작함
- 주요 구조 : 모듈 declarations, requires, exports 지시어
- 기존 자바 아카이브(JAR)에 적용되는 자동 모듈
- 모듈화와 JDK 라이브러리
- 모듈과 메이븐 빌드
- 기본적인 requires, exports 외의 모듈 지시어 간단 요약
자바 9에서 추가된 모듈 시스템은 프로젝트를 모듈로 분리하고 모듈 간 의존성을 명시하는 기능입니다. 이 기능을 통해 개발자는 모듈 간 의존성 충돌 문제를 해결하고 불필요한 라이브러리 의존성을 제거할 수 있습니다. 또한 모듈 시스템을 이용해 애플리케이션의 실행 속도를 개선하고 보안을 강화할 수 있습니다.
14.1 압력 : 소프트웨어 유추
이번 절에서는 자바 모듈 시스템을 살펴보기 전, 소프트웨어 아키텍처에서 유지보수하기 쉬운 코드를 구현하는 데 필요한 기능에 대해 이해하고자 합니다. 모듈화란 무엇이며, 어떤 문제를 해결할 수 있는가에 대해 설명하며, 추론하기 쉬운 소프트웨어를 만드는 데 도움을 주는 관심사분리와 정보 은닉에 대해 다룹니다.
14.1.1 관심사분리
관심사 분리는 컴퓨터 프로그램을 고유의 기능으로 나누는 원칙으로, 클래스를 그룹화한 모듈을 이용해 애플리케이션의 클래스 간 관계를 시각적으로 파악할 수 있습니다. SoC는 모델, 뷰, 컨트롤러와 같은 아키텍처 관점에서 비즈니스 로직과 분리하는 등의 상황에 유용하며, 개별 기능을 쉽게 작업하고 협업할 수 있고, 재사용과 유지보수가 용이해집니다. SoC의 주요 장점은 개별 기능의 쉬운 작업과 협업, 개별 부분의 쉬운 재사용, 전체 시스템의 쉬운 유지보수성입니다.
14.1.2 정보 은닉
정보 은닉은 세부 구현을 숨기도록 장려하는 원칙으로, 코드를 관리하고 보호하기 위한 중요한 역할을 합니다. 캡슐화는 특정 코드 조각이 애플리케이션의 다른 부분과 고립되어 있음을 의미하며, 내부적인 변화가 외부에 영향을 미치는 가능성을 줄입니다. 자바에서는 private 키워드를 사용해 적절하게 캡슐화를 구현할 수 있으며, 클래스와 패키지가 의도된 대로 공개되었는지를 확인하는 기능은 자바 9 이전에는 제공되지 않았습니다.
14.1.3 자바 소프트웨어
자바에서는 정보 은닉과 관심사 분리 두 가지 원칙을 따르는 것이 중요하며, 클래스, 인터페이스 등을 이용하여 코드를 그룹화하고 시각적 도구를 이용하여 코드 간 의존성을 보여줌으로써 소프트웨어를 추론하는 데 도움을 줍니다. 이전에는 접근 제한자와 패키지 수준 접근 권한 등을 이용하여 정보 은닉을 구현했지만, 이는 원하는 접근 제한을 달성하기 어려운 문제가 있었습니다. 따라서 자바 9부터는 모듈 시스템이 도입되어 애플리케이션을 모듈화 할 수 있게 되었습니다. 모듈화를 통해 클래스 간 의존성을 명확히 하고 필요한 모듈만 가져와 사용할 수 있게 되며, 정보 은닉과 같은 원칙을 더 쉽게 지킬 수 있게 되었습니다.
14.2 자바 모듈 시스템을 설계한 이유
이번 절에서는 자바 9 이전의 모듈화 한계와 JDK 라이브러리와 관련한 배경 지식을 바탕으로 자바 언어와 컴파일러에 추가된 새로운 모듈 시스템의 필요성과 중요성을 설명합니다.
14.2.1 모듈화의 한계
자바 9 이전까지는 모듈화 된 소프트웨어 프로젝트를 만드는 데 한계가 있었으며, 클래스와 관련된 캡슐화는 지원했지만 패키지와 JAR 수준에서는 캡슐화를 거의 지원하지 않았다.
제한된 가시성 제어
자바는 public, protected, 패키지 수준, private 등의 가시성 접근자를 제공하지만, 패키지 간의 가시성 제어가 어려워 기본 구현을 제공하는 패키지에서 내부 구현이 마음대로 사용될 수 있어 기존 애플리케이션을 망가뜨릴 위험이 있으며 보안적으로도 위험할 수 있습니다.
클래스 경로
자바는 클래스 경로와 JAR 조합에 약점이 있어서 의존성과 버전 관리가 어렵습니다. 클래스 경로에 같은 클래스를 구분하는 버전 개념이 없어서 라이브러리 버전이 다르면 예측할 수 없는 문제가 발생할 수 있고, 명시적인 의존성을 지원하지 않아 어떤 일이 일어나는지 파악하기 어렵습니다. 이러한 문제를 해결하기 위해 자바 9에서는 모듈 시스템을 도입했습니다. 모듈 시스템을 이용하면 컴파일 타임에 에러를 검출할 수 있고, 의존성 관리와 버전 관리가 쉬워집니다.
14.2.2 거대한 JDK
JDK는 자바 개발 및 실행에 필요한 도구들의 집합이다. 그러나 많은 기술들이 추가되었다가 사라지면서 JDK는 커져갔고, 이로 인해 불필요한 부분들이 포함되었다. 이를 해결하기 위해 자바 8에서는 세 가지 프로파일을 통해 라이브러리 메모리 풋프린트를 조절하는 방식을 제안했지만, 이는 땜질식 해결책이었다. JDK의 많은 내부 API는 외부에 공개되지 않아야 하지만, 낮은 캡슐화 지원으로 인해 내부 API가 외부에 노출되어 호환성을 깨지 않고는 API를 변경하기가 어려워졌다. 이러한 문제로 인해 JDK는 모듈화 할 수 있는 자바 모듈 시스템 설계가 필요해졌다. 이러한 모듈 시스템은 필요한 부분만 골라서 사용하고, 클래스 경로를 쉽게 유추할 수 있으며, 강력한 캡슐화를 제공하여 플랫폼을 진화시킬 수 있다.
14.3 자바 모듈 : 큰 그림
자바 8에서는 모듈이라는 새로운 프로그램 구조 단위를 제공하며, 모듈은 module이라는 새 키워드로 이름과 바디를 추가하여 정의됩니다. 모듈 디스크립터는 module-info.java 파일에 저장되며, 패키지를 서술하고 캡슐화할 수 있으며, 간단한 상황에서는 외부에 노출시킬 패키지를 선택하여 사용할 수 있습니다. 이를 통해 자바 모듈 시스템은 강력한 캡슐화를 제공하며, 필요한 부분만 선택적으로 사용하고 클래스 경로를 유추하기 쉬워집니다.
다음 그림은 자바 모듈 디스크립터의 핵심 구조를 보여줍니다.
14.4 자바 모듈 시스템으로 애플리케이션 개발하기
이번 절에서는 자바 9 모듈 시스템에 대해 전반적으로 살펴보며, 간단한 모듈화 애플리케이션을 만들어 구조화하고 패키지하며 실행하는 방법을 배우는 과정을 안내합니다. 상세한 내용은 다루지 않지만 모듈 시스템의 개요를 이해할 수 있도록 도와줍니다.
14.4.1 애플리케이션 셋업
자바 모듈 시스템을 적용하기 위해, 비용 관리 애플리케이션을 예제 프로젝트로 사용할 예정입니다. 이 애플리케이션은 여러 작업을 수행해야 하는데, 여행, 식료품 쇼핑, 커피 등의 비용을 기록하고 관리하는 기능을 제공합니다.
애플리케이션은 다음과 같은 여러 작업을 처리해야 합니다.
- 파일이나 URL에서 비용 목록을 읽는다.
- 비용의 문자열 표현을 파싱 한다.
- 통계를 계산한다.
- 유용한 요약 정보를 표시한다.
- 각 태스크의 시작, 마무리 지점을 제공한다.
애플리케이션을 모델링하기 위해 Reader 인터페이스, Parser 인터페이스, 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
이 간단한 애플리케이션에서는 모듈 시스템의 장점을 보여주기 위해 작은 기능까지 캡슐화하여 분해했습니다. 이러한 분해 작업은 초기 비용이 높아지지만, 프로젝트가 커질수록 추론과 캡슐화의 장점이 두드러지게 됩니다. 각 모듈은 내부 구현을 숨기기 위해 다른 모듈에 노출하고 싶지 않은 패키지를 포함할 수 있으며, 어떤 것을 릴리스할지는 나중에 결정할 수 있습니다.
14.4.2 세부적인 모듈화와 거친 모듈화
시스템을 모듈화 할 때 모듈 크기 결정은 중요하며, 세부적인 모듈화와 거친 모듈화는 각각 이득과 단점이 있습니다. 가장 좋은 방법은 시스템을 실용적으로 분해하면서 이해하기 쉽고 고치기 쉬운 수준으로 적절하게 모듈화 하고, 주기적으로 확인하는 것입니다.
14.4.3 자바 모듈 시스템 기초
기본적인 모듈화 애플리케이션은 단일 모듈을 사용하며, 중첩된 프로젝트 디렉터리 구조를 사용합니다. 모듈 디스크립터는 모듈의 의존성과 노출할 기능을 정의하며, 최상위 수준의 module-info.java 파일에 이름만 정의되어 있습니다. 모듈화 애플리케이션을 실행하기 위해 하위 수준의 동작을 이해하는 명령어를 살펴볼 수 있으며, IDE와 빌드 시스템에서 자동으로 처리되지만, 명령어를 이해하고 실행 과정을 파악하는 것이 내부 동작을 이해하는 데 도움이 됩니다.
14.5 여러 모듈 활용하기
모듈을 이용한 기본 애플리케이션을 설정한 상태에서, 비용 애플리케이션에서 소스에서 비용을 읽을 수 있도록 한 기능을 캡슐화한 expense.reader 모듈을 새로 만들 예정입니다. expenses.application와 expenses.readers 모듈 간의 상호 작용은 자바 9에서 지정한 export, requires를 이용해 구현될 것입니다.
14.5.1 exports 구문
다음은 expenses.readers 모듈의 선언입니다.
module expenses.readers {
// 이들은 모듈명이아니라 패키지명입니다.
exports com.example.expenses.readers;
exports com.example.expenses.readers.file;
exports com.example.expenses.readers.http;
}
exports 구문은 특정 패키지를 공개 형식으로 만들어 다른 모듈에서 사용할 수 있도록 합니다. 모듈 시스템에서는 기본적으로 모든 것이 캡슐화되어 있으므로, 다른 모듈에서 사용할 수 있는 기능을 명시적으로 결정해야 합니다. 이를 위해 exports 구문을 사용합니다.
14.5.2 requires 구분
또는 다음처럼 module-info.java를 구현할 수 있습니다.
module expenses.readers {
requires java.base; // 패키지명이 아니라 모듈며입니다.
// 모듈명이 아니라 패키지명입니다.
export com.example.expenses.reaeders;
export com.example.expenses.reaeders.file;
export com.example.expenses.reaeders.http;
}
requires 구문은 의존하는 모듈을 지정하는 데 사용됩니다. 모든 모듈은 기본적으로 java.base 플랫폼 모듈에 의존하며, 이 모듈은 자바의 메인 패키지를 포함합니다. requires 구문은 java.base 이외의 모듈을 임포트 할 때 사용되며, 이를 통해 클래스 접근을 보다 정교하게 제어할 수 있습니다.
14.5.3 이름 정하기
모듈의 이름 규칙은 인터넷 도메인명을 역순으로 시작하며, 모듈명은 노출된 주요 API 패키지와 이름이 같아야 한다는 규칙을 따라야 합니다. 모듈이 패키지를 포함하지 않거나 노출된 패키지와 이름이 일치하지 않는 경우를 제외하면, 작성자의 인터넷 도메인명을 역순으로 시작해야 합니다. 여러 모듈을 프로젝트에 설정한 후 패키지하고 실행하는 방법은 14.6절에서 다룹니다.
14.6 컴파일과 패키징
모든 모듈은 독립적으로 컴파일되기 때문에 각 모듈에 pom.xml을 추가해야 합니다. 또한, 전체 프로젝트 빌드를 조정하기 위해 모든 모듈의 부모 모듈에도 pom.xml을 추가해야 합니다. 그레이들도 다른 유명한 빌드 도구이므로 살펴볼 것을 권장합니다.
14.7 자동 모듈
httpclient 외부 라이브러리도 의존성을 기술하여 모듈로 사용할 수 있다. 모듈화가 되어있지 않은 라이브러리도 자동 모듈이라는 형태로 적절하게 변환한다. 다만, 자동 모듈은 암묵적으로 자신의 모든 패키지를 노출한다.
14.8 모듈 정의와 구문들
모듈 정의에는 requires, exports 외에도 requires-transitive, exports-to, open, opens, uses, provides 등 다양한 구문이 있습니다.
14.8.1 requires
requires 구문은 한 모듈이 다른 모듈에 컴파일 타임과 런타임에 의존함을 정의하는 구문입니다. 예를 들어 com.iteratrlearning.application 모듈은 com.iteratrlearning.io 모듈에 의존한다는 것을 requires 구문으로 정의할 수 있습니다.
module com.iteratrlearning.application {
requires com.iteratrlearning.ui;
}
그러면 com.iteratrlearning.ui에서 외부로 노출한 공개 형식을 com.iteratrlearning.application에서 사용할 수 있습니다.
14.8.2 exports
exports 구문은 공개할 패키지를 지정하여 다른 모듈에서 이용할 수 있도록 만듭니다. 이 구문을 사용하지 않으면 어떤 패키지도 공개하지 않는 것이 기본 설정입니다. 공개할 패키지를 명시적으로 지정함으로 캡슐화를 높일 수 있습니다. 예를 들어 com.iteratrlearning.ui 패키지에서 com.iteratrlearning.ui.panels와 com.iteratrlearning.ui.widgets를 공개하는 방법은 다음과 같습니다.
module com.iteratrlearning.ui {
requires com.iteratrlearning.core;
exports com.iteratrlearning.ui.panels;
exports com.iteratrlearning.ui.widgets;
}
14.8.3 requires transitive
requires-transitive 구문을 사용하면 한 모듈이 의존하는 다른 모듈이 사용하는 모든 공개 형식을 자동으로 사용할 수 있도록 지정할 수 있습니다. 다른 모듈이 제공하는 공개 형식을 한 모듈에서 사용할 수 있다고 지정할 수 있습니다.
module com.iteratrlearning.uui {
requires transitive com.iteratrlearning.core;
exports com.iteratrlearning.ui.panels;
exports com.iteratrlearning.ui.widgets;
}
module com.iteratrlearning.application {
requires com.iteratrlearning.ui;
}
requires-transitive 구문을 사용하면 다른 모듈에서 제공하는 공개 형식에 접근할 수 있습니다. 예를 들어, com.iteratrlearning.ui 모듈에서 requires-transitive 구문을 사용하여 com.iteratrlearning.core 모듈에서 노출한 공개 형식에 접근할 수 있습니다. 이를 이용해 다른 모듈에서 반환한 형식에 접근하는 상황에서 선언을 다시 할 필요 없이 문제를 해결할 수 있습니다.
14.8.4 exports to
export to 구문을 이용하면 사용자에게 공개할 기능을 제한하여 가시성을 더 정교하게 제어할 수 있습니다. 이를 위해 com.iteratrlearning.ui.widgets 패키지를 exports to 구문을 이용해 com.iteratrlearning.ui.widgetuser 모듈에만 노출할 수 있습니다.
module com.iteratrlearning.ui {
requires com.iteratrlearning.core;
exports com.iteratrlearning.ui.panels;
exports com.iteratrlearning.ui.widgets to
com.iteratrlearning.ui.widgetuser;
}
14.8.5 open과 opens
open 한정자를 이용하면 모든 패키지를 다른 모듈에 반사적으로 접근을 허용할 수 있습니다. 이외에 open 한정자는 모듈의 가시성에 다른 영향을 미치지 않습니다.
open module com.iteratrlearning.ui {
}
이전 버전에서는 리플렉션을 이용해 객체의 비공개 상태를 확인할 수 있었지만, 자바 9에서는 이러한 기능을 제한하기 위해 새로운 접근 제어 방식을 제공합니다. 이를 위해 open 구문을 사용하거나 opens 구문으로 필요한 개별 패키지만 개방할 수 있습니다. 또한, opens 구문을 사용하면 exports-to와 같이 특정 모듈에만 반사적인 접근을 허용할 수 있습니다.
14.8.6 uses와 provides
자바 모듈 시스템에서는 provides 구문으로 서비스를 제공하고, uses 구문으로 서비스를 소비하는 기능을 제공합니다. 이는 서비스와 ServiceLoader에 익숙한 독자라면 친숙한 내용입니다. 하지만 이는 고급 주제에 해당하며 이 장에서는 다루지 않습니다.
14.9 더 큰 예제 그리고 더 배울 수 있는 방법
자바 모듈 시스템의 다양한 기능들을 활용하여 작성된 예제 코드를 보여주고 있다는 것을 알려주는 요약입니다.
module com.example.foo {
requires com.example.foo.http;
requires java.logging;
requires transitive com.example.foo.network;
exports com.example.foo.bar;
exports com.example.foo.interanl to com.example.foo.probe
opens com.example.foo.quux;
opens com.example.foo.internal to com.example.foo.network,
com.example.foo.probe;
uses com example.foo.spi.Intf;
provides com.example.foo.spi.Intf with com.example.foo.Impl;
}
14장에서는 자바 모듈 시스템의 필요성과 기능에 대해 간단하게 설명했으며, 서비스 로더, 모듈 서술자, jeps, jlink와 같은 도구에 대해서는 자세히 다루지 않았습니다. 또한, 자바 EE 개발자들은 자바 9에서 모듈화 된 자바 가상 머신에서 기본 클래스 경로에 자바 EE API 패키지가 포함되지 않는다는 사실을 알고 있어야 하며, 호환성을 유지하기 위해서는 필요한 모듈을 명시적으로 추가해야 한다는 점을 인지해야 합니다.
'📚 서적 및 학습자료 > 모던 자바 인 액션[2회독]' 카테고리의 다른 글
[모던 자바 인 액션] 16장 CompletableFuture : 안정적 비동기 프로그래밍 (0) | 2023.04.02 |
---|---|
[모던 자바 인 액션] 15장 CompletableFuture와 리액티브 프로그래밍 컨셉의 기초 (0) | 2023.03.29 |
[모던 자바 인 액션] 13장 디폴트 메서드 (0) | 2023.03.26 |
[모던 자바 인 액션] 12장 새로운 날짜와 시간 API (0) | 2023.03.26 |
[모던 자바 인 액션] 11장 null 대신 Optional 클래스 (0) | 2023.03.25 |