개발자 포포

[Java] JSR 376 - Java Platform Module System(Jigsaw)

popo.se 2024. 9. 16. 23:17
이 글은 과거에 운영하던 블로그에서 옮겨온 글 입니다. (2021.11.07. 작성됨)

Java Platform Module System

Java 9의 가장 큰 특징은 Jigsaw 프로젝트를 통한 Java Platform Module System 도입입니다.

Jigsaw 프로젝트의 주요 내용은 다음과 같습니다.

1. Modular JDK

  • JDK를 작은 단위의 모듈로 분리
  • Java 9 이상에서 java --list-modules 명령어를 사용하면 모듈화된 jdk를 확인할 수 있습니다.

[참고] JEP 200 - The Modular JDK

 

[Java 11 환경에서 커맨드 실행한 내역]

(base) Mac-mini:bin PPoya$ java --list-modules
java.base@11.0.10
java.compiler@11.0.10
java.datatransfer@11.0.10
java.desktop@11.0.10
java.instrument@11.0.10
java.logging@11.0.10
java.management@11.0.10
java.management.rmi@11.0.10
java.naming@11.0.10
java.net.http@11.0.10
java.prefs@11.0.10
java.rmi@11.0.10
java.scripting@11.0.10
java.se@11.0.10
java.security.jgss@11.0.10
java.security.sasl@11.0.10
java.smartcardio@11.0.10
java.sql@11.0.10
java.sql.rowset@11.0.10
java.transaction.xa@11.0.10
java.xml@11.0.10
java.xml.crypto@11.0.10
jdk.accessibility@11.0.10
jdk.aot@11.0.10
jdk.attach@11.0.10
jdk.charsets@11.0.10
jdk.compiler@11.0.10
jdk.crypto.cryptoki@11.0.10
jdk.crypto.ec@11.0.10
jdk.dynalink@11.0.10
jdk.editpad@11.0.10
jdk.hotspot.agent@11.0.10
jdk.httpserver@11.0.10
jdk.internal.ed@11.0.10
jdk.internal.jvmstat@11.0.10
jdk.internal.le@11.0.10
jdk.internal.opt@11.0.10
jdk.internal.vm.ci@11.0.10
jdk.internal.vm.compiler@11.0.10
jdk.internal.vm.compiler.management@11.0.10
jdk.jartool@11.0.10
jdk.javadoc@11.0.10
jdk.jcmd@11.0.10
jdk.jconsole@11.0.10
jdk.jdeps@11.0.10
jdk.jdi@11.0.10
jdk.jdwp.agent@11.0.10
jdk.jfr@11.0.10
jdk.jlink@11.0.10
jdk.jshell@11.0.10
jdk.jsobject@11.0.10
jdk.jstatd@11.0.10
jdk.localedata@11.0.10
jdk.management@11.0.10
jdk.management.agent@11.0.10
jdk.management.jfr@11.0.10
jdk.naming.dns@11.0.10
jdk.naming.ldap@11.0.10
jdk.naming.rmi@11.0.10
jdk.net@11.0.10
jdk.pack@11.0.10
jdk.rmic@11.0.10
jdk.scripting.nashorn@11.0.10
jdk.scripting.nashorn.shell@11.0.10
jdk.sctp@11.0.10
jdk.security.auth@11.0.10
jdk.security.jgss@11.0.10
jdk.unsupported@11.0.10
jdk.unsupported.desktop@11.0.10
jdk.xml.dom@11.0.10
jdk.zipfs@11.0.10

2. Modular Source Code

  • JDK 소스 코드를 작은 단위의 모듈로 분리하고 모듈간 경계를 명확히 합니다.

[참고] JEP 201 - Modular Source Code

3. Modular Run-Time Images

  • Java 9 부터는 별도의 JRE 패키지가 존재하지 않고 JDK로만 배포됩니다. (jdk 서브 디렉토리 /jre 삭제)
    • Java 9 이전에는 자바 실행에 사용하는 JRE와 실행에 더불러 개발 도구를 지원해주는 JDK가 각각 존재했습니다.
  • JDK를 재구성하고 모듈화 하였습니다.
    • Java 9 이전의 JDK
      • JDK는 내부적으로 JRE를 복사하여 서브 디렉토리로 가지고 있었습니다.
      • JDK는 JRE 서브 디렉토리 외에 bin, demo, man, include, lib 등의 디렉토리를 더 가지고 있었습니다.
      • bin에는 커맨드 라인으로 사용할 수 있는 개발과 디버깅 관련 툴이 있었습니다. jre/bin 하위에 있는 것과 중복되는 것이 많았습니다.
    • 아래와 같은 파일들을 모듈화 및 효율적인 포맷으로 변환하여 jdk에 재구성하여 위치시켰습니다.
      • JVM 실행시 부트스트랩 클래스 로더에서 로딩하던 /jre/lib/rt.jar
      • javac 컴파일러를 포함하고 있었던 /lib/tools.jar(tools.jar는 javac 컴파일러 클래스들을 포함하고 있었기 때문에, tools.jar에 의존하여 자바를 컴파일 하던 것들에서 오류가 날 수 있습니다.)
    • extension 메카니즘을 삭제하였습니다.
      • extension 메카니즘은 Java SE 플랫폼을 확장하기 위한 JAR를 런타임 시에 참조하기 위해서 존재했었습니다.
      • 이 메카니즘에 따라 Extension ClassLoader는 자바의 시스템 변수인 java.ext.dirs의 경로에 있는 JAR들에 대해 클래스 로딩을 했었습니다. (java.ext.dirs의 기본 경로는 $JAVA_HOME/lib/ext 였습니다.)
      • extension 메카니즘은 jdk 1.2에 도입되었던 것인데, 오늘날은 보통 런타임 시스템 경로에 확장 라이브러리를 다운받아 두고 쓰기 보다는, 보통 해당 애플리케이션의 class path에 두고 바로 접근하여 사용하므로 사용성이 떨어진다 생각하게 되었습니다.

[참고] JEP 220 - Modular Run-Time Images

4. Encapsulate Most Internal APIs

대부분의 내부 API들을 캡슐화 하였습니다.

[참고] JEP 260 - Encapsulate Most Internal APIs

5. Module System

Java 9 이후부터 애플리케이션은 반드시 모듈로 패키징 되어야 합니다.

[참고] JEP 261 - Module System

6. jlink: The Java Linker

[참고] JEP 282 - Modular Run-Time Images

 

Jigsaw 프로젝트를 통해 개선된 점은 다음과 같습니다.

  • JVM start-up 타임에 만약 필요한 모듈을 못 찾으면 missing module을 report하고 종료하므로 런타임 오류를 방지합니다.
    • Java 9 이전에는 애플리케이션이 런타임시 특정 클래스를 사용하는 시점에서 해당 클래스가 있는지를 체크하여 프로그램 실행 중 missing classes 오류 발생 가능성이 있었습니다.
  • 불필요하고 비인가된 모듈 간 결합을 방지하여 모듈의 올바른 사용을 유도
    • Java 9 이전에는 애플리케이션이 사용하지 않더라도 XML, SQL, Swing 같은 패키지를 항상 같이 배포해야 했습니다.
  • 보안성 향상
  • 캡슐화를 통한 느슨한 결합으로 유지관리성 향상
  • 성능 최적화
  • 경량화된 패키징 및 배포
    • 용량이 작은 컴퓨터나 라즈베리파이와 같은 소형 기기에서 보다 효율적으로 프로그램으로 실행 가능

 

모듈 간 참조 예제 테스트

프로젝트 구조

1. com.jigsawtest1

module-info.java

module com.jigsawtest1 {
  exports jigsaw.car;
}
  • jigsawtest1 모듈에서 car 패키지에 대해서만 다른 모듈에서 참조할 수 있도록 합니다.

jigsaw/car/Car.java

package jigsaw.car;

public class Car {
    private int price;
    private String carName;

    public Car(int price, String carName) {
        this.price = price;
        this.carName = carName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }

    public void changeCarName(String newName){
        carName="new_"+newName;
    }

    @Override
    public String toString() {
        return "Car{" +
                "price=" + price +
                ", carName='" + carName + '\\'' +
                '}';
    }
}

jigsaw/car/notexported/PrivateCar.java

package jigsaw.car.notexported;

public class PrivateCar {
    private int price;
    private String carName;

    public PrivateCar(int price, String carName) {
        this.price = price;
        this.carName = carName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }

    public void changeCarName(String newName){
        carName="new_"+newName;
    }

    @Override
    public String toString() {
        return "Car{" +
                "price=" + price +
                ", carName='" + carName + '\\'' +
                '}';
    }
}

2. org.jigsawtest2

module-info 정의

module org.jigsawtest2 {
    requires com.jigsawtest1;
}
  • jigsawtest1 모듈을 import 합니다.

jigsaw/person/Person.java

package jigsaw.person;

import jigsaw.car.Car;

public class Person {
    String name;
    int age;
    Car car;

    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

jigsaw/person/jigsawTest.java

package jigsaw.person;

import jigsaw.car.Car;// module-info에 정의되어있으므로 다른 모듈이어도 사용 가능//import jigsaw.car.notexported.PrivateCar;public class jigsawTest {

    public static void main(String[] args){
        Car car=new Car(10000,"super car");
        System.out.println(car);
        Person person=new Person("jerry",30,car);
        person.car.changeCarName("bmw");
        System.out.println(car);

// PrivateCar privateCar = new PrivateCar(10000,"super car"); 사용 불가
    }
}

 

 

위 코드에서 PrivateCar privateCar = new PrivateCar(10000,"super car"); 주석을 해제하면 아래와 같은 오류가 발생합니다.

 

jigsaw.car.notexported.PrivateCar 클래스가 com.jigsawtest1 모듈에서 exports 하지 않기 때문에 발생하는 오류입니다.

이와 같이 모듈과 모듈 간 결합에 있어서 제약 사항을 줄 수 있어서 캡슐화와 보안성을 지킬 수 있습니다.

 

[참고]