본문 바로가기

IT Book Summary/ModernJavaInAction

Chapter 14 자바 모듈 시스템

자바9에서 가장 많이 거론되는 새로운 기능이다.

모듈시스템은 Jigsaw 프로젝트 내부에서 개발된 십년이 걸린 기능.

 

14.1 압력: 소프트웨어 유추

 

1 - 관심사 분리

Separation of Concern 은 컴퓨터 프로그램을 고유의 기능으로 나누는 동작을 권장하는 원칙

만약 회계프로그램을 작성한다고 하면, SoC를 적용하여

파싱, 분석, 레포트 기능을 모듈 각각으로 서로 겹치지 않는 코드그룹으로 분리 가능하다.

클래스를 그룹화한 모듈을 이용해 어플리케이션의 클래스간의 관계를 시각적으로 보여줄 수 있다.

 

  • 개별 기능을 따로 작업할 수 있고 팀이 쉽게 협업한다
  • 개별 부분을 재사용하기 쉽다
  • 전체 시스템을 쉽게 유지보수 할 수 있다.

 

2 - 정보 은닉

세부구현을 숨기도록 장려하는 원칙

이 원칙이 중요한 이유는?

개발시 요구사항이 자주 바뀌기 때문.

세부구현을 숨김으로써 프로그램의 부분을 바꿨을때 다른 부분까지 영향을 미칠 가능성을 줄일 수 있다.

캡슐화된 코드의 내부적 변화가 외부에 끼칠 영향을 줄일 수 있다.

 

클래스 내 private 키워드를 사용했는지 기준으로 컴파일러를 이용해 캡슐화를 확인할 수 있는데,

자바9 이전까지 클래스와 패키지가 의도된대로 공개되었는지 컴파일러로 확인할 수 있는 기능이 없었다.

 

3 - 자바 소프트웨어

자바는 객체지향 언어로 특정문제와 관련된 패키지, 클래스, 인터페이스를 그룹으로 만들어 코드를 그룹화할 수 있다.

코드만 보고 동작을 추론하기 어려우니 UML 다이어그램을 이용하면 시각적으로 코드 의존성을 보여줄 수 있다.

 


14.2 자바 모듈 시스템을 설계한 이유

 

1 - 모듈화의 한계

 

이전에는 클래스, 패키지, JAR 세가지 수준의 코드 그룸화를 제공하였다.

클래스 관련해 접근제한자와 캡슐화를 지원했지만

패키지와 JAR 수준에서는 캡슐화를 거의 지원하지 않았다.

 

제한된 가시성 제어

public, protected, default, private 네가지 가시성 접근자 

한 패키지의 클래스와 인터페이스를 다른 패키지로 공개하려면 public으로 선언해야 하기에

결과적으로 클래스와 인터페이스는 모두 공개된다.

 

클래스 경로

클래스를 모두 컴파일 후 보통 한개의 jar 파일에 넣고 클래스 경로에 JAR 파일을 추가해 사용할 수 있다.

그러면 JVM이 동적으로 클래스 경로에 정의된 클래스를 필요할 때 읽는다.

 

클래스 경로에는 같은 클래스를 구분하는 버전 개념이 없음.

다양한 컴포넌트가 같은 라이브러리의 다른 버전을 사용할 경우 문제

 

클래스 경로는 명시적인 의존성을 지원하지 않음.

각각의 JAR 안에 있는 모든 클래스는 classes로 합쳐진다.

한 JAR 이 다른 JAR에 포함된 클래스 집합을 사용하라고 명시적으로 의존성을 정의하는 기능을 제공하지 않음.

 

따라서 메이븐이나 그레들 같은 빌드 도구를 사용해 문제를 해결한다.

자바 자체적으로는 명시적 의존성 정의를 지원하지 않았다.

 

2 - 거대한 JDK

자바 프로그램을 만들고 실행하는데 도움을 주는 도구의 집합.

- 자바 프로그램을 컴파일하는 javac, 애플리케이션을 로드하고 실행하는 java, 입출력 호함 런타임 지원을 제공하는 JDK 라이브러리,컬렉션,스트림 등.

 

JDK 자체도 모듈화할 수 있는 자바 모듈 시스템 설계의 필요성 제기

JDK에서 필요한 부분만 골라쓰고, 클래스 경로를 쉽게 유추할 수 있으며,

플랫폼을 진화시킬 수 있는 강력한 캡슐화를 제공할 건축구조 필요.

 

3 - OSGi와 비교

자바의 공식 기능은 아니지만 JVM에서 모듈화 애플리케이션을 구현하는 표준으로 자리잡음.

번들 동작에 필요한 외부패키지가 무엇이며 어떤 내부 패키지가 외부로 노출되 다른번들로 제공되는지 서술 정의하는 텍스트파일.

OSGi의 각 번들이 자체적 클래스 로더를 갖는 반면,

자바9 모듈 시스템의 직소는 애플리케이션당 한개의 클래스 사용

 


14.3 자바 모듈 : 큰 그림

 

자바8은 모듈이라는 새로운 자바 프로그램 구조단위를 제공.

module이라는 새 키워드에 이름과 바디를 추가해서 정의.

모듈 디스크립터 ( module-info.class에 저장된 바이너리 형식)는 moduel-info.java라는 파일에 저장.

 

module 모듈명

exports 패키지명

requires 모듈명

 

메이븐 같은 도구에서 모듈의 많은 세부사항을 IDE가 처리함.


14.4 자바 모듈 시스템으로 애플리케이션 개발하기

 

예제 프로젝트에서 모듈 시스템을 적용해 보자

 

1 - 애플리케이션 셋업

 

ex) 비용처리 애플리케이션

- 파일이나 URL에서 비용 목록을 읽는다

- 비용의 문자열 표현을 파싱한다

- 통계를 계산한다

- 유용한 요약 정보를 표시한다

- 각 태스크의 시작, 마무리 지점을 제공한다

 

개념을 모델링 할 여러 클래스와 인터페이스 정의

  • 다양한 소스에서 데이터를 읽음 (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

 

2 - 세부적인 모듈화와 거친 모듈화

모듈화시 모듈의 크기를 결정해야 한다.

세부적 모듈화 기법은 모든 패키지가 자신의 모듈을 갖는다.

거친 모듈화 기법은 한 모듈이 시스템의 모든 패키지를 포함한다.

 

가장 좋은 방법은 이해하기 쉽고 고치기 쉽게 적절하게 모듈회되어있는지 주기적으로 확인하는 것.

 

3 - 자바 모듈 시스템 기초

 

모듈화 애플리케이션을 어떻게 실행할 수 있을까?

보통 IDE와 빌드 시스템에서 명령을 자동으로 처리하지만 어떤 동작을 수행하는지 살펴보자

 

expenses.application

L module-info.java

L com

   L example

      L expenses

         L application

           L ExpensesApplication.java

 

최상위에 module-info.java 는 이름만 정의되어있고 아직 비어있다.

이 파일은 모듈 디스크립터로 모듈의 의존성, 외부에 어떤기능을 노출할지 정의한다.

 

모듈 소스 디렉터리에서 다음 명령을 실행한다.

javac module-info.java com/example/expenses/application/ExpensesApplication.java -d target

jar cvfe expenses-application.jar com.example.expenses.application.ExpensesApplication -D target

 

생성된 JAR을 모듈화 애플리케이션으로 실행

java --module-path expenses-application jar --module expenses/com.example.expenses.application.ExpensesApplication

 

자바 .class  파일을 실행할 때 다음과 같은 두가지 옵션이 새로 추가되었다.

--module-path: 어떤 모듈을 로드할 수 잇는지 지정.

--module: 실행할 메인 모듈과 클래스를 지정.

 


14.5 여러 모듈 활용하기

 

 

1 - exports 구문

 

module expenses.readers {
    exports com.example.expenses.readers;
    exports com.example.expenses.readers.file;
    exports com.example.expenses.readers.http;
    // 패키지명
}

exports는 다른 모듈에서 사용할 수 있도록 특정 패키지를 공개 형식으로 만든다.

 

2 - requires 구분

의존하고 있는 모듈을 지정.

module expenses.readers {
    requires java.base; // 모듈명

    exports com.example.expenses.readers;
    exports com.example.expenses.readers.file;
    exports com.example.expenses.readers.http;
    // 패키지명
}

java.base는 항상 기본적으로 필요한 모듈이므로 명시적으로 지정할 필요는 없다

 

3 - 이름 정하기

 

패키지명처럼 인터넷 도메인명 역순으로 모듈 이름을 정하도록 권고한다.


14.6 컴파일과 패키징

 

메이븐등의 빌드 도구를 이용해 프로젝트를 컴파일 한다.

 

각 모듈안에 하나씩 pom.xml 을 추가하고 부모모듈에도 pom.xml 추가.

 

자식모듈안에는 의존성이 필요한 모듈의 dependencies를 설정해줌.

 

최상위 pom.xml에는 각 모듈을 정의해 준다.

 

mvn clean package 명령을 실행해 프로젝트의 모듈을 JAR로 만든다.

 

./expenses.application/target/expenses.application-1.0.jar

./expenses.reader/ target/expenses.reader-1.0.jar

두개의 JAR 만들어짐.

 

두 JAR을 모듈경로에 포함해서 모듈 애플리케이션을 실행

java --module-path \

./expenses.application/target/expenses.application-1.0.jar \

./expenses.reader/ target/expenses.reader-1.0.jar \

 --moduel \ 

expenses.application/com.example.expenses.application.ExpensesApplication


14.7 자동 모듈

 

HttpReader를 구현하지않고 아파치의 httpclient 라이브러리를 사용해 구현하려면 어떻게 추가할까?

 

module-info.java 에 requires 구문으로 추가하고,

의존성을 pom.xml에 갱신해야한다. 

 

자바는 JAR을 자동 모듈이라는 형태로 적절히 변환한다.

모듈경로상에 있으나 module-info.java 가 없는 모든 JAR은 자동 모듈된다. 

 

--describe-module 인수를 이용해 자동으로 정해지는 모듈이름을 바꿀수 있다.

jar --file=./expenses.reader/target/dependency/httpclient-4.5.3.jar \

     --describe-module httpclient@4.5.3 automatic

 

그리고 httpclient JAR을 모듈경로에 추가한 다음 애플리케이션을 실행

java --module-path \

./expenses.application/target/expenses.application-1.0.jar \

./expenses.reader/target/expenses.reader-1.0.jar \

./expenses.readers/target/dependency/httpclient-4.5.3.jar \

 --moduel \ 

expenses.application/com.example.expenses.application.ExpensesApplication

 


14.8 모듈 정의와 구문들

 

 

1 - requires

컴파일 타임과 런타임에 한 모듈이 다른 모듈에 의존함을 정의한다.

모듈명을 인수로 받음.

module com.iteratrlearning.application {
    requires com.iteratrlearning.ui;
}

com.iteratrlearning.application은 com.iteratrlearning.ui 모듈에 의존함.

 

2 - exports

지정한 패키지를 다른 모듈에서 이용할 수 있도록 공개형식으로 만든다.

패키지명을 인수로 받음.

어떤 패키지를 공개할 것인지 명시적으로 지정해 캡슐화를 높인다.

 

3 - requires transitive

다른 모듈이 제공하는 공개 형식을 한 모듈에서 사용할 수 있다고 지정.

module com.iteratrlearning.ui {
    requires transitive com.iteratrlearning.core;
    
    exports com.iteratrlearning.ui.panels;
    exports com.ireratrlearning.ui.widgets;
}

module com.iteratrlearning.application {
    requires com.iteratrlearning.ui;
}

com.iteratrlearning.application 모듈은 com.iteratrlearning.core에 노출한 공개 형식에 접근할 수 있다.

 

4 - exports to

사용자에게 공개할 기능을 제한함으로써 가시성을 좀 더 정교하게 제어

module com.iteratrlearning.ui {
    requires com.iteratrlearning.core;
    
    exports com.iteratrlearnig.ui.panels;
    exports com.iteratrlearnig.ui.widgets to com.iteratrlearnig.ui.widgetuser;
}

com.iteratrlearnig.ui.widgets의 접근 권한을 가진 사용자의 권한을 com.iteratrlearnig.ui.widgetuser로 제한할 수 있다.

 

5 - open 과 opens

모듈선언에 open 한정자를 이용하면 모든 패키지를 다른 모듈에 반사적으로 접근을 허용할 수 있다.

opens 구문을 모듈 선언에 이용해 필요한 개별 패키지만 개방할 수 있음.

exports-to 로 노출한 패키지를 사용할 수 있는 모듈을 한정했던 것처럼, open to로 반사적 접근을 특정모듈에만 허용가능.

 

6 - uses 와 provides

provides 구문으로 서비스 제공자를 uses구문으로 서비스 소비자를 지정할 수 있는 기능을 제공.

 


14.9 더 큰 예제 그리고 더 배울 수 있는 방법

 

ex) 오라클 자바문서에서 가져온 예제

module com.example.foo {
    requires com.example.foo.http;
    requires java.logging;
    
    requires transitive com.example.foo.network;
    
    exports com.example.foo.quux;
    exports com.example.foo.internal 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;
}

 

자바 모듈시스템을 더 알고싶으면 'The Java Module System' 책을 보자

 


  • 관심사분리와 정보 은닉은 추론하기 쉬운 소프트 웨어를 만드는 중요한 원칙
  • 자바9 이전에 패키지, 클래스, 인터페이스로 모듈화를 구현하기엔 효과적 캡슐화 달성하기 역부족.
  • 클래스 경로 지옥 문제는 애플리케이션 의존성을 추론하기 더 어렵게 했다.
  • 자바9 이전의 JDK는 거대했으며 높은 유지 비용과 진화를 방해하는 문제가 존재
  • 자바9에서는 새로운 모듈 시스템을 제공하는데 module-info.java 파일은 모듈의 이름을 지정해 필요한 의존성과 공개 API를 정의
  • requires 구문으로 필요한 다른 모듈 정의
  • exports 구문으로 특정 패키지를 다른 모듈에서 사용할 수 있는 공개 형식으로 지정
  • 인터넷 도메인명을 역순으로 사용하는것이 권장 모듈 이름 규칙
  • 모듈 경로에 포함된 JAR 중에 module-info 파일을 포함하지 않는 모든 JAR는 자동 모듈이 됨.
  • 자동 모듈은 암묵적으로 모든 패키지를 공개
  • 메이븐은 자바 9 모듈 시스템으로 구조화된 애플리케이션을 지원