자바 모듈 시스템

김종준·2023년 2월 27일
0

모던자바

목록 보기
11/15

자바 모듈 시스템

새로운 언어 기능을 통해 유지보수하기 쉬운 코드를 구현하는 것은 저수준 영역에 해당한다.

소프트웨어 아키텍처 즉 고수준 영역에서는 기반 코드를 바꿔야 할 때 유추하기 쉬우므로 생산성을 높일 수 있는 소프트웨어 프로젝트가 필요하다.

추론하기 쉬운 소프트웨어를 만드는 데 도움을 주는 것이 관심사 분리정보 은닉이다.

관심사 분리(SoC)

관심사 분리는 컴퓨터 프로그램을 고유의 기능으로 나누는 동작을 권장하는 원칙이다.

자바 9 모듈은 클래스가 어떤 다른 클래스를 볼 수 있는지를 컴파일 시간에 정교하게 제어할 수 있다.

SoC 원칙은 모델, 뷰, 컨트롤러 같은 아키텍처 관점 그리고 복구 기법을 비즈니스 로직과 분리하는 등의 하위 수준 접근 등의 상황에 유용하다.

SoC 원칙은 다음과 같은 장점을 제공한다.

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

정보 은닉

정보 은닉은 세부 구현을 숨기도록 장려하는 원칙이다.

즉, 코드를 관리하고 보호하는 데 유용한 원칙이다.

캡슐화는 특정 코드 조각이 애플리케이션의 다른 부분과 고립되어 있음을 의미한다.

캡슐화된 코드의 내부적인 변화가 의도치 않게 외부에 영향을 미칠 가능성이 줄어든다.

자바에서는 클래스 내의 컴포넌트에 적절하게 private 키워드를 사용했는지를 기준으로 컴파일러를 이용해 캡슐화를 확인할 수 있다.

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

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

모듈화의 한계

자바는 클래스, 패키지, JAR 세 가지 수준의 코드 그룹화를 제공한다.

클래스와 관련해 자바는 접근 제한자와 캡슐화를 지원했다.

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

제한된 가시성 제어

많은 애플리케이션은 다양한 클래스 그룹을 정의한 여러 패키지가 있는데 패키지의 가시성 제어 기능은 유명무실한 수준이다.

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

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

만약 내부적으로 사용할 목적으로 만든 구현을 다른 프로그래머가 임시로 사용해서 정착해버릴 수 있으므로 결국 기존의 애플리케이션을 망가뜨리지 않고 라이브러리 코드를 바꾸기 어려워진다.

보안 측면에서 볼 때 코드가 노출되었으므로 코드를 임의로 조작하는 위협에 더 많이 노출될 수 있다.

클래스 경로

자바는 애플리케이션을 번들하고 실행하는 기능과 관련해 태생적으로 약점을 갖고 있다.

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

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

클래스 경로와 JAR 조합에는 몇 가지 약점이 존재한다.

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

둘째, 클래스 경로는 명시적인 의존성을 지원하지 않는다.

자바 모듈

모듈은 module아리는 새 키워드에 이름과 바디를 추가해서 정의한다.

모듈 디 스크립터는 module-info.java라는 특별한 파일에 저장된다.

모듈 디 스크립터는 보통 패키지와 같은 폴더에 위치하며 한 개 이상의 패키지를 서술하고 캡슐화할 수 있지만 단순한 상황에서는 이들 패키지 중 한 개만 노출한다.

자바 모듈 시스템 기초

module-info.java라는 파일이 프로젝트 구조의 일부에 포함되어 있다.

해당 파일은 앞에서 설명한 모듈 디 스크립터로 모듈의 소스 코드 파일 루트에 위치해야 하며 모듈의 의존성 그리고 어떤 기능을 외부로 노출할지를 정의한다.

exports 구문

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

기본적으로 모듈 내의 모든 것은 캡슐화된다.

모듈 시스템은 화이트 리스트 기법을 이용해 강력한 캡슐화를 제공하므로 다른 모듈에서 사용할 수 있는 기능이 무엇인지 명시적으로 결정해야 한다.

requires 구분

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

모듈 정의와 구문들

requires

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

module com.A.B {
  requires com.A.C;
}

com.A.C에서 외부로 노출한 공개 형식을 com.A.B에서 사용할 수 있다.

exports

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

module com.A.C {
  requires com.A.D; 
  exports com.A.E; 
  exports com.A.F;
}

위의 예제에서 주의할 것은 exprots패키지명을 인수로, requires모듈명을 인수로 받는다는 것이다.

requires transitive

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

module com.A.C {
  requires transitive com.A.D; 
  exports com.A.E; 
  exports com.A.F;
}
module com.A.G {
  requires com.A.C;
}

com.A.G 모듈은 com.A.D 에서 노출한 공개 형식에 접근할 수 있다.

필요로 하는 모듈이 다른 모듈의 형식을 반환하는 상황에서 전이성(transitive) 선언을 유용하게 사용할 수 있다.

exports to

exports to 구문을 이용해 사용자에게 공개할 기능을 제한함으로 가시성을 좀 더 정교하게 제어할 수 있다.

module com.A.C {
  requires com.A.D; 
  exports com.A.E; 
  exports com.A.F to com.A.C.H;
}

open과 opens

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

open module com.A.C {
  
}

uses와 provides

자바 모듈 시스템은 provides 구문으로 서비스 제공자를 uses 구문으로 서비스 소비자를 지정할 수 있는 기능을 제공한다.

0개의 댓글