1. 소개

Project Jigsaw 는 다음 두 가지 측면을 목표로 하는 새로운 기능을 갖춘 포괄적인 프로젝트입니다.

  • Java 언어의 모듈 시스템 도입
  • JDK 소스 및 Java 런타임에서의 구현

이 기사에서는 Jigsaw 프로젝트와 그 기능을 소개하고 마지막으로 간단한 모듈식 응용 프로그램으로 마무리합니다.

2. 모듈화

간단히 말해서 모듈성은 다음을 달성하는 데 도움이 되는 설계 원칙입니다.

  • 구성 요소 간의 느슨한 결합
  • 구성 요소 간의 명확한 계약 및 의존성
  • 강력한 캡슐화를 사용한 숨겨진 구현

2.1. 모듈화 단위

이제 모듈화의 단위가 무엇인지에 대한 질문이 나옵니다. Java 세계, 특히 OSGi에서 JAR은 모듈성의 단위로 간주되었습니다.

JAR은 관련 구성 요소를 함께 그룹화하는 데 도움이 되었지만 몇 가지 제한 사항이 있습니다.

  • JAR 간의 명시적 계약 및 의존성
  • JAR 내 요소의 약한 캡슐화

2.2. JAR 지옥

JAR에는 또 다른 문제가 있었습니다. 바로 JAR 지옥이었습니다. 클래스 경로에 있는 여러 버전의 JAR로 인해 ClassLoader 가 JAR에서 처음으로 발견된 클래스를 로드하고 매우 예상치 못한 결과를 얻었습니다.

클래스 경로를 사용하는 JVM의 다른 문제는 애플리케이션 컴파일은 성공하지만 런타임 시 클래스 경로에 JAR이 누락되어 ClassNotFoundException 과 함께 애플리케이션이 런타임에 실패한다는 것 입니다.

2.3. 새로운 모듈화 단위

이러한 모든 제한 사항이 있지만 JAR을 모듈화 단위로 사용할 때 Java 언어 작성자는 모듈이라는 언어로 새로운 구성을 생각해 냈습니다. 그리고 이를 통해 Java용으로 계획된 완전히 새로운 모듈식 시스템이 있습니다.

3. 프로젝트 퍼즐

이 프로젝트의 주요 동기는 다음과 같습니다.

  • JEP 261에 따라 구현된 언어에 대한 모듈 시스템 생성
  • JDK 소스에 적용JEP 201에 따라 구현
  • modularize the JDK libraries – implemented under JEP 200
  • update the runtime to support modularity – implemented under JEP 220
  • be able to create smaller runtime with a subset of modules from JDK – implemented under JEP 282

Another important initiative is to encapsulate the internal APIs in the JDK, those who are under the sun.* packages and other non-standard APIs. These APIs were never meant to be used by public and were never planned to be maintained. But the power of these APIs made the Java developers leverage them in the development of different libraries, frameworks, and tools. There have been replacements provided for few internal APIs and the others have been moved into internal modules.

4. New Tools for Modularity

  • jdeps – helps in analyzing the code base to identify the dependencies on JDK APIs and the third party JARs. It also mentions the name of the module where the JDK API can be found. This makes it easier in modularizing the code base
  • jdeprscan – helps in analyzing the code base for usage of any deprecated APIs
  • jlink – helps in creating a smaller runtime by combining the application's and the JDK's modules
  • jmod – helps in working with jmod files. jmod is a new format for packaging the modules. This format allows including native code, configuration files, and other data that do not fit into JAR files

5. Module System Architecture

The module system, implemented in the language, supports these as a top level construct, just like packages. Developers can organize their code into modules and declare dependencies between them in their respective module definition files.

A module definition file, named as module-info.java, contains:

  • its name
  • the packages it makes available publicly
  • the modules it depends on
  • any services it consumes
  • any implementation for the service it provides

The last two items in the above list are not commonly used. They are used only when services are being provided and consumed via the java.util.ServiceLoader interface.

A general structure of the module looks like:

src
 |----com.baeldung.reader
 |     |----module-info.java
 |     |----com
 |          |----baeldung
 |               |----reader
 |                    |----Test.java
 |----com.baeldung.writer
      |----module-info.java
           |----com
                |----baeldung
                     |----writer
                          |----AnotherTest.java

The above illustration defines two modules: com.baeldung.reader and com.baeldung.writer. Each of them has its definition specified in module-info.java and the code files placed under com/baeldung/reader and com/baeldung/writer, respectively.

5.1. Module Definition Terminologies

Let us look at some of the terminologies; we will use while defining the module (i.e., within the module-info.java):

  • module: the module definition file starts with this keyword followed by its name and definition
  • requires: is used to indicate the modules it depends on; a module name has to be specified after this keyword
  • transitive: is specified after the requires keyword; this means that any module that depends on the module defining requires transitive <modulename> gets an implicit dependence on the <modulename>
  • exports: is used to indicate the packages within the module available publicly; a package name has to be specified after this keyword
  • opens: is used to indicate the packages that are accessible only at runtime and also available for introspection via Reflection APIs; this is quite significant to libraries like Spring and Hibernate, highly rely on Reflection APIs; opens can also be used at the module level in which case the entire module is accessible at runtime
  • uses: is used to indicate the service interface that this module is using; a type name, i.e., complete class/interface name, has to specified after this keyword
  • provides … with ...: they are used to indicate that it provides implementations, identified after the with keyword, for the service interface identified after the provides keyword

6. Simple Modular Application

Let us create a simple modular application with modules and their dependencies as indicated in the diagram below:

The com.baeldung.student.model is the root module. It defines model class com.baeldung.student.model.Student, which contains the following properties:

public class Student {
    private String registrationId;
    //other relevant fields, getters and setters
}

It provides other modules with types defined in the com.baeldung.student.model package. This is achieved by defining it in the file module-info.java:

module com.baeldung.student.model {
    exports com.baeldung.student.model;
}

The com.baeldung.student.service module provides an interface com.baeldung.student.service.StudentService with abstract CRUD operations:

public interface StudentService {
    public String create(Student student);
    public Student read(String registrationId);
    public Student update(Student student);
    public String delete(String registrationId);
}

It depends on the com.baeldung.student.model module and makes the types defined in the package com.baeldung.student.service available for other modules:

module com.baeldung.student.service {
    requires transitive com.baeldung.student.model;
    exports com.baeldung.student.service;
}

We provide another module com.baeldung.student.service.dbimpl, which provides the implementation com.baeldung.student.service.dbimpl.StudentDbService for the above module:

public class StudentDbService implements StudentService {

    public String create(Student student) {
        // Creating student in DB
        return student.getRegistrationId();
    }

    public Student read(String registrationId) {
        // Reading student from DB
        return new Student();
    }

    public Student update(Student student) {
        // Updating student in DB
        return student;
    }

    public String delete(String registrationId) {
        // Deleting student in DB
        return registrationId;
    }
}

com.baeldung.student.service 에 직접적으로 의존하고 com.baeldung.student.model 에 전이적으로 의존 하며 그 정의는 다음과 같습니다.

module com.baeldung.student.service.dbimpl {
    requires transitive com.baeldung.student.service;
    requires java.logging;
    exports com.baeldung.student.service.dbimpl;
}

마지막 모듈은 클라이언트 모듈입니다. 이 모듈은 서비스 구현 모듈 com.baeldung.student.service.dbimpl활용하여 작업을 수행합니다.

public class StudentClient {

    public static void main(String[] args) {
        StudentService service = new StudentDbService();
        service.create(new Student());
        service.read("17SS0001");
        service.update(new Student());
        service.delete("17SS0001");
    }
}

그리고 그 정의는 다음과 같습니다.

module com.baeldung.student.client {
    requires com.baeldung.student.service.dbimpl;
}

7. 샘플 컴파일 및 실행

Windows 및 Unix 플랫폼용으로 위의 모듈을 컴파일하고 실행하기 위한 스크립트를 제공했습니다. 이들은 core-java-9 프로젝트 여기 에서 찾을 수 있습니다 . Windows 플랫폼의 실행 순서는 다음과 같습니다.

  1. 컴파일 학생 모델
  2. 컴파일 학생 서비스
  3. 컴파일 학생 서비스 dbimpl
  4. 컴파일 학생 클라이언트
  5. 실행 학생 클라이언트

Linux 플랫폼의 실행 순서는 매우 간단합니다.

  1. 컴파일 모듈
  2. 실행 학생 클라이언트

위의 스크립트에서 다음 두 가지 명령줄 인수를 소개합니다.

  • – 모듈 소스 경로
  • – 모듈 경로

Java 9는 클래스 경로의 개념을 없애고 대신 모듈 경로를 도입합니다. 이 경로는 모듈을 검색할 수 있는 위치입니다.

명령줄 인수 –module-path 를 사용하여 이를 설정할 수 있습니다 .

한 번에 여러 모듈을 컴파일하려면 –module-source-path 를 사용 합니다. 이 인수는 모듈 소스 코드의 위치를 ​​제공하는 데 사용됩니다.

8. JDK 소스에 적용된 모듈 시스템

모든 JDK 설치는 src.zip 과 함께 제공됩니다 . 이 아카이브에는 JDK Java API에 대한 코드 기반이 포함되어 있습니다. 아카이브를 추출하면 여러 폴더를 찾을 수 있습니다. 몇 개는 java로 시작하고 몇 개는 javafx로 나머지는 jdk로 시작합니다. 각 폴더는 모듈을 나타냅니다.

java시작하는 모듈 은 JDK 모듈이고, javafx시작하는 모듈은 JavaFX 모듈이고, jdk시작하는 다른 모듈 은 JDK 도구 모듈입니다.

모든 JDK 모듈과 모든 사용자 정의 모듈은 암시적으로 java.base 모듈 에 의존 합니다. java.base의 모듈은 일반적으로의 Utils, 컬렉션, IO, 다른 사람의 사이에서 동시성과 같은 JDK API를 사용 포함되어 있습니다. JDK 모듈의 의존성 그래프는 다음과 같습니다.

또한 JDK 모듈의 정의를 보고 module-info.java 에서 정의하기 위한 구문에 대한 아이디어를 얻을 수 있습니다.

9. 결론

이 기사에서는 간단한 모듈식 애플리케이션을 생성, 컴파일 및 실행하는 방법을 살펴보았습니다. 또한 JDK 소스 코드가 어떻게 모듈화되었는지 보았습니다.

링커 도구인 jlink를 사용하여 더 작은 런타임을 만들고 다른 기능 중에서 모듈식 항아리를 만드는 것과 같은 몇 가지 흥미로운 기능이 더 있습니다. 향후 기사에서 해당 기능에 대해 자세히 소개하겠습니다.

Project Jigsaw는 엄청난 변화이며, 개발자 생태계, 특히 도구 및 라이브러리 제작자가 이를 어떻게 받아들일지 지켜봐야 합니다.

이 기사에 사용된 코드 는 GitHub 에서 찾을 수 있습니다 .

Junit footer banner