14장: 자바 모듈 시스템

모듈의 필요성과 사용 방법을 알아본다.

모듈의 필요성

관심사 분리

  • SoC(Separation of concerns)로 줄여 말하며, 컴퓨터 프로그램을 고유의 기능으로 나누는 동작을 권장하는 원칙

  • 각각의 기능에 따라 코드 그룹을 분리할 수 있다.

  • 자바 9 모듈은 클래스가 어떤 다른 클래스를 볼 수 있는지 컴파일 타임에 제어할 수 있다.

  • 개별 기능을 따로 작업할 수 있으므로 협업에 용이하고, 재사용에 용이하고, 전체 시스템의 유지 보수에 좋다.

정보 은닉

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

  • 프로그램의 어떤 부분을 바꿨을 때 다른 부분에 영향을 미칠 가능성을 줄여 코드를 관리하고 보호할 수 있다.

  • private 등의 접근 제한자를 적절하게 사용했는지를 기준으로 캡슐화를 확인할 수 있지만, 클래스와 패키지가 의도된 대로 공개되었는지 컴파일러로 확인할 수 없다.

  • 기존에는 클래스 수준의 접근 제한자, 캡슐화만 지원하고 패키지, JAR 수준에서는 캡슐화를 거의 지원하지 않는다.

클래스 경로

  • 클래스 경로에는 같은 클래스를 구분하는 버전 개념이 없어 클래스 경로에 버전이 다른 같은 라이브러리가 존재하면 어떤 일이 일어날지 예측할 수 없다.

  • 클래스 경로는 명시적인 의존성을 지원하지 않아 어떤 JAR가 다른 JAR에 포함된 클래스 집합을 사용할 것을 명시적으로 정의할 수 없다.

  • 자바 9 이전에는 자바, JVM에서 명시적인 의존성 정의를 지원하지 않아 컴파일 타임에 클래스 경로 에러를 잡기 어려웠다.

  • Maven이나 Gradle 같은 빌드 도구가 이런 문제를 해결하는데에 도움을 준다.

거대한 JDK

  • 자바 개발 키트(JDK)는 자바 프로그램을 만들고 실행하는데 도움을 주는 도구의 집합이다.

  • JDK 의 많은 내부 API 는 공개되지 않아야 하는데 자바 언어의 낮은 캡슐화 지원으로 인해 외부에 공개되었다.

  • 예를 들어 sun.misc.Unsafe 클래스는 JDK 내부용으로 만든 클래스이지만 Spring, Netty, Mockito에서 사용되어 하위 호환성을 깨지 않고는 API를 바꾸기 어려운 상황이 되었다.

자바의 모듈

  • 자바 8에서는 모듈이라는 새로운 자바 프로그램 구조 단위를 제공한다.

  • 모듈을 정의하기 위해서는 module 키워드에 이름과 바디를 추가해야 한다.

  • 모듈 디스크립터는 패키지와 같은 위치에 존재하는 module-info.java 라는 파일에 저장되며, 여러 패키지를 서술하고 캡슐화할 수 있긴 하지만 일반적으로 한 개 패키지만 노출시킨다.

  • Maven, Gradle을 사용하는 경우 IDE가 모듈의 많은 세부사항을 처리하여 사용자에게는 잘 드러나지 않는다.

  • 모듈 디스크립터는 아래와 같은 구조로 구성되어 모듈의 의존성과 어떤 기능을 외부로 노출할 지 정의할 수 있다.

exports 구문

  • 모듈 시스템은 화이트 리스트 기법으로 캡슐화를 제공하므로, 다른 모듈에서 사용할 수 있는 기능을 명시해야 한다.

  • 아래는 readers라는 패키지에 module-info.java 를 작성하고, 해당 패키지 하위에 존재하는 특정 패키지를 export하여 다른 모듈에서 사용할 수 있도록 한다.

패키지 구조는 expenses.readers.com.example.request.readers.file 과 같이 구성된 경우를 가정한다.

module expenses.readers {
    exports com.example.request.readers;
    exports com.example.request.readers.file;
    exports com.example.request.readers.http;
}
  • exports to 구문을 이용해 사용자에게 공개할 기능을 제한할 수 있다.

module com.iteratrlearning.ui {
    // widgets 접근 권한을 가진 사용자의 권한을 widgetuser 로 제한 
    exports com.iteratrlearning.ui.widgets to
      com.iteratrlearning.ui.widgetuser;
}

requires 구문

  • 해당 모듈이 의존할 모듈을 지정한다.

  • 모든 모듈은 java.base라는 플랫폼 모듈에 의존하는데, 이는 net, io, util 등의 자바 메인 패키지를 포함한다. 다만 이는 명시적으로 정의할 필요는 없다.

module expenses.readers {
    requires java.base;
    
    exports com.example.expenses.readers;
    exports com.example.expenses.readers.file;
    exports com.example.expenses.readers.http;  
}
  • transitive 키워드를 함께 사용하여 다른 모듈이 제공하는 공개 형식을 한 모듈에서 사용할 수 있다고 지정할 수 있다.

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

module com.iteratrlearning.application {
    // com.iteratrlearning.core 에도 접근 가능
    requires com.iteratrlearning.ui;
}

open 구문

  • open 한정자를 사용해 모든 패키지를 다른 모듈에 reflective한 접근을 허용할 수 있다. 자바 9 에서는 리플렉션으로 객체의 비공개 상태를 확인할 수 없기 때문에 해당 기능이 필요하면 open 구문을 명시하고 사용해야 한다.

  • opens 구문을 사용해 필요한 개별 패키지만 개방할 수 있다. (to 키워드로 특정 모듈만 지정 가능)

open module com.iteratrlearning.ui {
}

module com.iteratrlearning.ui {
    opens com.iteratrlearning.ui.panels
}

uses / provides 구문

  • provides 구문을 사용해 서비스 제공자를 지정할 수 있다.

  • uses 구문을 사용해 서비스 소비자를 지정할 수 있다.

모듈명

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

  • 모듈명은 노출된 주요 API 패키지와 이름이 같아야 한다.

컴파일과 패키징

  • 모듈은 독립적으로 컴파일 되기 때문에 자체적으로 각각이 한 개의 프로젝트이다.

  • Maven을 사용할 겨웅 전체 프로젝트 빌드를 위해 모든 모듈의 부모 모듈(전역 모듈)에 pom.xml 을 추가하고, 각 모듈마다도 pom.xml을 추가해주어야 한다.

  • 전역 모듈에는 아래와 같이 하위 모듈을 참조한다.

<modules>
    <module>expenses.application</module>
    <module>expenses.readers</module>
</modules>
  • 각 모듈에는 부모 모듈을 추가한다.

    <parent>
        <groupId>com.example</groupId>
        <artifactId>expenses</artifactId>
        <version>1.0</version>
    </parent>
  • 다른 모듈을 참조하고자 하면 아래와 같이 의존성을 추가하면 된다.

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>expenses.readers</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
  • mvn clean package 명령으로 모듈을 각각의 jar 로 생성할 수 있으며, 두 jar 을 모듈 경로에 포함해 모듈 애플리케이션을 실행한다.

java --module-path \
./expenses.application/target/expenses.application-1.0.jar:\
./expenses.readers/target/expenses.readers-1.0.jar \
  --module expenses.application/com.example.expenses.application.ExpensesApplication

자동 모듈

  • 자바는 JAR 를 자동 모듈이라는 형태로 적절하게 변환한다. 모듈 경로 상에 있으나 module-info.java 파일을 가지지 않은 모든 JAR는 자동 모듈이 된다.

Last updated