본문 바로가기

JAVA

Migrate to Java9

Migrate to Java9

1995년 이래로 Java는 끊임없이 발전하여 세계에서 가장 많이 사용되는 프로그래밍 언어중 하나가 되었습니다. 그리고, 2017년 Java9 이 출시 되었습니다.

각 Java 릴리스에는 크고 작은 기능이 추가 되었고 Java9도 예외는 아닙니다.

현재 응용프로그램을 Java9로 마이그레이션 할 시기를 찾고 있다면 계획을 행동에 옮기기 전에 중요한 몇가지 사항을 고려해야 합니다. 여기서는 Java9로 마이그레이션 하기 위해 해결해야 할 일반적인 문제를 다룹니다.

Java9로 마이그레이션을 진행할때 살펴볼 중요한 주제는 다음과 같습니다.

  • Java8 에서 Java9 로 마이그레이션하는 방법 과 이를 보다 쉽게 수행하는 방법
  • JDeps 도구를 사용하여 코드를 검사하는 방법
  • Java Jigsaw 모듈로 마이그레이션
  • 캡슐화된 내부 API, 분할 패키지, 순환 종속성 등과 같은 마이그레이션 문제를 해결하는 방법
  • 호환성을 제공하기 위해 모듈 경로와 classpath를 결합하는 방법

Java Jigsaw 모듈로 응용프로그램을 마이그레이션 하기 위한 첫 단계

Java9로 마이그레이션하기 위해 수행해야하는 몇가지 기본 준비 사항이 있습니다. (현재 Java8 을 사용중이라고 가정합니다.)

  1. JDK9 를 설치 하세요.
  2. classpath 를 변경하세요.
  3. JDK9 를 사용하여 프로그램을 실행하고, 오류 및 경고 메세지를 확인하세요.
  4. 모든 써드 파티 라이브러리와 툴을 JDK9 를 지원하는 버전으로 업그레이드 하세요.
  5. JDK9 에서 다시 빌드 하세요.
  6. JDeps 도구를 사용하여 사용중인 API 를 Java9 를 지원하는 다른 API 로 대체할 수 있는지 확인하세요.

이렇게 해서 문제 없이 동작한다면 매우 쉽습니다.

그러나 이미 알고 있듯이 JDK 내부 API 는 더 이상 액세스 할 수 없습니다. JDeps 를 사용하여 코드에서 JDK 내부 클래스를 사용중인지 확인해야 합니다.

JDeps 는 통계적 종속성을 분석하고 찾는 도구 입니다. JDeps 가 코드에서 종속성을 발견하면 힌트와 대안을 제안합니다.

그러나 이러한 클래스를 제거하거나 대체 할 라이브러리를 결정하는 것은 온전히 여러분의 책임입니다.

JDK 내부 클래스에 대한 적절한 대체를 찾으려는 시도에 실패했다면, 컴파일러 명령행 옵션으로 -add-exports를 사용하세요. 이 경우 코드를 변경할 필요가 없습니다. 이 명령은 캡슐화를 해제하고 코드에서 내부 API 에 액세스 할 수 있게 하기 때문에 컴파일 할때 이 옵션을 추가하도록 스크립트를 조정하면 됩니다.

rt.jar 및 tools.jar 에 대한 종속성을 Java9에서 모두 제거 했으므로 꼭 코드를 확인해야 합니다.

 

Java9로 마이그레이션 할때 발생할 수 있는 문제점

Java 가 수년간 성공을 거두는데 가장 강력한 효과를 발휘한것 중 하나는 각 Java 버전간 호환성입니다. 그러나 모듈화가 적용된 Java9 는 호환성이 100% 보장되지 않습니다. (사용한 라이브러리 및 코드 구조에 따라 다릅니다.)

따라서, Java9로 마이그레이션 하기전에 달성하려는 결과를 아는 것이 중요합니다.

  1. Java 애플리케이션을 JDK9 에서 간단하게 실행하고 코드 내에 모듈을 정의하고 싶지는 않습니다. (classpath만 사용)
  2. 응용 프로그램의 일부만 모듈화하고 나머지는 모듈화하지 않길 원합니다. (classpath와 모듈 경로를 사용)
  3. 전체 응용 프로그램을 모듈화 하려고 합니다. (모듈 경로만 사용)

첫번째 경우에는 JAVA_HOME 환경 변수를 JDK9 를 가리키도록 설정한 다음 코드를 변경하지 않고 컴파일하고 실행합니다. 즉, classpath에 머물면서 모듈 경로를 전혀 사용하지 마세요.

그러나, classpath에만 의존하는 단점이 있으며, 그 중 하나는 사용자정의 런타임 이미지를 만들 수 없습니다.

두번째 방법은 응용 프로그램의 일부만 모듈화 하고 나머지 부분은 유지를 해서 모듈 경로와 classpath를 함께 사용합니다. 하지만, 기본적으로 모듈 경로의 코드는 classpath에 배치된 코드에 액세스 할 수 없습니다. 이 문제를 해결하는데 최소한 두가지 방법이 있습니다.

  • classpath에서 코드를 가져와서 자동 모듈로 변환하는 것. 특히 모듈화되지 않은 타사 JAR 에 유용합니다.
  • -add-exports 옵션 사용. 이 옵션는 다른 모듈이나 classpath에서 액세스 할 수 있게 해줍니다.

세번째 경우는 응용프로그램의 완전한 모듈화 입니다. 결과적으로 classpath를 더 이상 사용하지 않습니다.

자동 모듈이란 무엇인가요?

오라클은 개발자가 Java8 에서 Java9 로 성공적으로 마이그레이션 할 수 있도록 다음과 같이 3가지 유형의 모듈을 도입하여 보다 쉽게 수행할 수 있도록 지원합니다.

  1. 이름없는 모듈은 classpath에 JAR 파일을 배치하여 생성됩니다 (즉, 현재 Java 환경에서 일어나는 일).
  2. 자동 모듈은 JAR 파일이 새 모듈 경로에 배치 될 때 작성됩니다.
  3. 명시적/명명된 모듈은 module-info.java에 수동으로 정의됩니다.

자동 모듈은 JAR 파일을 모듈 경로에 배치한 후 자동으로 생성되는 특수 유형의 명명된 모듈입니다. 기존 응용 프로그램을 Java 9로 쉽게 마이그레이션하고 역 호환성을 달성하는 데 사용됩니다. 또한 필요한 모든 라이브러리와 프레임워크가 모듈화되는 동안 기다리지 않고도 사용자 코드를 모듈화할 수 있습니다.

예를 들어, 널리 퍼져 있는 Log4j 라이브러리를 살펴보겠습니다. 모듈 경로에 Log4j JAR 파일을 넣으면 응용 프로그램의 모듈 설명자 내부에 요구하여 모듈에서 사용할 수 있습니다.

 

이 예제에서는 Log4j를 자동 모듈로 전환하고 모듈화 응용 프로그램에서 사용할 수 있습니다. 자동 모듈은 기본적으로 모든 패키지를 export 하므로 Log4j 모듈의 모든 패키지에 액세스 할 수 있습니다.

또한 자동 모듈에는 몇 가지 중요한 특성이 있음을 알아야 합니다.

  • 모든 패키지를 export 하고 open 합니다.

    • 자동 모듈의 모든 패키지는 컴파일 타임과 런타임 모두에 액세스 할 수 있도록 export 됩니다.
    • 자동 모듈의 모든 패키지는 deep reflection 을 통해 액세스 할 수 있도록 open 됩니다.
  • 최상위 디렉토리에 module-info.class 파일이 없습니다 .

  • 이름이 없는 모듈(classpath에 존재하는)의 모든 클래스에 액세스 할 수 있습니다.

  • 다른 모듈에 대한 종속성이 있음을 선언 할 수 없습니다.

게다가 자동 모듈을 사용할 때 그 이름은 Jigsaw에 의해 JAR 파일 이름에서 생성됩니다. 파일 이름 기반 알고리즘의 주요 측면은 다음과 같습니다.

  • .jar 접미어는 JAR 파일의 이름에서 제거됩니다. 결과 문자열은 자동 모듈의 이름과 버전을 결정하고 추출하는 데 더 사용됩니다.
  • 모듈 이름이 추출됩니다. JDK 9 API 문서 에 따르면 "이름이 정규 표현식((\\d+(\\.|$)))과 일치하면 모듈 이름은 첫 번째 발생 하이픈 앞에 오는 하위 시퀀스에서 파생됩니다. 하이픈 이후의 부분은 버전으로 구문 분석되며, 버전으로 구문 분석 할 수 없을 경우 무시됩니다. "
  • 모듈 이름에 대한 일부 대체가 수행됩니다. JDK 9 API 문서에는 "모듈 이름의 영숫자가 아닌 문자 ([^A-Za-z0-9])는 점 ( '.')으로 대체됩니다. 반복되는 모든 점은 하나의 점으로 대체됩니다. 선행 및 후행 점이 모두 제거됩니다. "

모듈 이름을 추출 할 수 없는 모듈 경로에 JAR을 배치 한 후에 치명적인 오류가 표시 될 준비가 되어 있어야합니다. 자동 모듈의 이름은 JAR 파일의 META-INF 디렉토리에있는 MANIFEST.MF 파일 내에서 직접 정의 할 수도 있습니다 .

 

이제 자동 모듈에 대해 알아야 할 모든 것을 검토 했으므로 라이브러리 의존성을 찾는 데 중요한 도구인 JDeps 도구를 고려해야 할 때입니다.

Java의 종속성 분석 도구로 부적절한 종속성을 찾습니다

당신의 코드에서 Sun 내부 패키지 (예 : sun.misc.* )를 사용하고 있습니까? 그렇다면 여러분에게 나쁜 소식이 있습니다. 새로운 제한 사항 때문에 Java 9에서 코드 실행이 중단됩니다. Sun 내부 패키지에 의존하지 않지만 이러한 패키지에 의존하는 라이브러리에 전이 종속성이있는 경우에도 중단되며 코드도 손상됩니다!

다행스럽게도 Oracle은 이러한 시나리오를 예견하고 JDeps라는 도구를 제공했습니다.

자, JDEP는 무엇입니까? Java Dependency Analysis Tool (JDeps)은 다양한 목적으로 사용되는 매우 유용한 명령 행 도구입니다.

  • 모든 정적 라이브러리 의존성을 발견합니다.
  • 내부 JDK API의 가능한 사용법을 발견합니다.
  • JAR 파일에 대한 모듈 설명자를 자동으로 생성합니다.

간단히 말해서, JDeps에는 -JDK-internals 옵션이 있습니다. 이 옵션은 지원되지 않는 JDK 내부 API 에 대한 종속성을 찾습니다. 구문은 다음과 같습니다.

 

입력으로 분석 할 JAR 파일이나 .class 파일을 지정할 수 있습니다 .

또한 JDeps는 발견 된 내부 API에 대한 대체를 제안합니다. 그리고 Java 8에서 Java 9로 성공적으로 마이그레이션 하고자 할 때 JDeps 도구를 사용하면 classpath의 JAR 파일이 JDK 내부 API를 사용하는지 여부를 확인할 수 있습니다.

캡슐화 된 JDK 9 내부 API

아시다시피 Java는 JDK에서 두 가지 API 범주를 지원 및 지원하지 않습니다.

지원되는 API는 java.* 및 javax.* 과 같은 JCP 표준 API , com.sun.* 및 jdk.* 같은 JDK 내부 API로 구성 됩니다.

지원되지 않는 API에는 sun.* 패키지가 포함됩니다. 이러한 API는 외부 사용을 위한 것이 아니었지만 많은 개발자가 JDK 외부에서 sun.* 패키지를 사용했습니다.

참고 : Oracle 연구에 따르면 가장 많이 사용되는 JDK 내부 클래스는 sun.misc.BASE64Encodersun.misc.BASE64Decoder 및 sun.misc.Unsafe입니다.

Java9는 거의 모든 JDK 내부 API를 캡슐화했습니다. 이것이 가장 큰 문제는 아니지만 Jdk 9 로 마이그레이션하는 동안 어려움을 겪게 될 것입니다. 이 과정에서 문제가 발생할 수 있습니다.

다행히 JDK 내부 API의 문제를 해결하는 데 도움이되는 두 가지 독립 솔루션이 있습니다.

  • 각 JDK 내부 API를 지원되는 API로 바꾸세요.
  • 기존 JDK 내부 API를 유지하고 -add-exports 명령 행 옵션을 사용하여 캡슐화를 중단하세요. JDK 내부 API를 다른 모듈의 코드 나 classpath에 코드에 액세스 할 수 있게 만듭니다.

Java 컴파일러 (javac)에 추가 된 -add-exports 옵션은 패키지를 특별히 명명된 모듈 또는 이름이 지정되지 않은 모듈로 export 합니다.

다음은 -add-exports 명령 행 옵션 의 구문입니다 .

 
  • <source_module> 은 export할 패키지가 있는 모듈을 나타냅니다.
  • <name_of_package_to_be_exported><list_of_target_modules>에 export할 패키지의 이름을 나타냅니다 .
  • <list_of_target_modules> 는 export한 패키지에 액세스 할 수 있는, 쉼표로 구분된 모듈 목록을 나타냅니다.

다음은 응용프로그램에서 어떻게 보이는지 보여주는 예입니다.

 

위의 스냅 샷에서 우리는 패키지가 있는 모듈 java.base 와 패키지를 export할 모듈을com.romexsoft.testmodule 에 전달합니다. 위에서 언급 한 -add-exports 옵션을 사용하여 애플리케이션을 컴파일 하면 sun.net 패키지가 com.romexsoft.testmodule 로 export됩니다.

어떤 부분이나 코드가 classpath 상에 있는 경우 전체 classpath를 나타내는 상수 ALL-UNNAMED 를 사용할 수 있습니다 . 예를 들어 아래 명령은 classpath의 전체 코드에서 액세스 할 수 있도록 sun.net 패키지를 classpath로 export 합니다.

 

또한 다른 옵션을 사용하여 JAR 파일의 MANIFEST.MF 파일 에서 Add-Exports 속성을 지정할 수 있습니다. 예를 들어,  sun.net 모듈에서 java.base 를 익명의 모듈로 export 합니다. 

 

요약하면 두 가지 방법 모두 문제를 해결합니다.

첫 번째 솔루션 (모든 JDK 내부 API 대체)은 코드에서 지원되지 않는 JDK API를 완전히 제거 할 때 가장 좋은 방법입니다. JDK 내부 API가 사용되지 않는 것으로 표시되어 있으므로 가능한 빨리 대체품을 제공하는 것이 좋습니다.

두 번째 해결책은 코드를 컴파일하고 JDK 9를 사용하여 다시 실행하기만 하면 됩니다. 그러나 Oracle은 다음 주요 JDK 릴리스에서 JDK 지원되지 않는 API가 제거 될 것이라고 말했습니다. JDK 10 이상일 수 있습니다. 따라서 -add-exports 옵션을 추가하는 것은 코드를 작동시키는 일시적인 해결책일뿐, 조만간 지원되는 JDK API로 대체해야 할 것입니다.

Opening Packages for Deep Reflection

이미 언급했듯이, deep reflection은 기본적으로 classpath의 코드에 대해 명명된 모듈에서 허용되지만 기본적으로 다른 명명된 모듈의 코드에서는 액세스 할 수 없습니다.

명명된 모듈의 코드에서 다른 명명된 모듈의 코드로 reflection 액세스를 허용하려면 -add-opens 옵션을 사용할 수 있습니다. 그것은 한 모듈에서 다른 모듈이나 classpath의 코드에 대한 deep reflection 액세스를 제공하는 데 사용됩니다. -add-opens 옵션의 구문은 다음과 같습니다.

 

<source_module> 에 정의되어있는 패키지는 <list_of_target_modules>에 나열된 모듈에 대한 deep reflection 액세스를 위해 열립니다 . 이러한 모듈은 deep reflection 만 사용하여 런타임에 패키지에 액세스 할 수 있지만 컴파일하는 동안에는 패키지에 액세스 할 수 없습니다.

<list_of_target_modules> 대신에 상수 ALL-UNNAMED 를 넣으면 classpath의 전체 코드는 deep reflection을 사용하여 런타임에 패키지에 액세스 할 수 있습니다.

deep reflection는 런타임에만 발생할 수 있습니다. 따라서 -add-opens 명령 행은 javac 명령을 사용하여 컴파일 할 때 사용할 수 없습니다.

패키지 분할 문제를 해결하는 방법

분할 된 패키지에 대해 자세히 설명하기 전에 Java9 모듈러 세계에서 발생할 수있는 가장 심각한 문제 중 하나이며 이러한 문제를 해결하는 데 많은 주의를 기울여야 할 필요가 있음을 말씀드립니다. 아래의 설명과 같이 분할 패키지 문제를 해결할 수있는 몇 가지 방법을 고려해 보았습니다. 접근 방식 중 하나를 선택하거나 자신의 방식으로 진행할 수 있습니다.

분할 패키지에 대해 특별한 점은 무엇입니까?

분할 패키지는 두 개 이상의 패키지 구성원이 두 개 이상의 모듈로 분할 될 때 발생합니다. 신뢰할 수있는 구성을 적용하기 위해 모듈은 두 개의 다른 모듈에서 동일한 패키지를 읽을 수 없습니다. 실제 구현은 더 엄격하지만 두 모듈 (같은 종류의 모듈 일지라도)이 동일한 패키지 (내보내기 또는 숨김)를 포함하는 것을 허용하지 않습니다.

기본적으로 모듈 시스템은 이 가정하에 작동하며 클래스를 로드해야 할 때 마다 이 패키지가 포함 된 모듈을 찾고 거기에서 클래스를 찾습니다. 그 이유는 시스템이 패키지의 단일 유형을 둘 이상 가질 수없는 단일 클래스 로더로 모듈 경로에서 모든 모듈을 로드하기 때문입니다. 모듈에 다른 하위 패키지가 포함되어있는 경우에도 동일한 이름의 패키지를 공유하므로 분할 패키지 문제가 발생합니다.

또한 플랫폼 모듈의 패키지로 인해 분할 패키지 문제가 발생할 수 있음을 언급하는 것도 중요합니다. 예를 들어, 이것은 플랫폼 모듈 중 하나에 이미 제시된 패키지 이름을 사용하는 모듈을 개발할 때 발생합니다. 패키지를 내보내거나 열거 나 은폐해도 문제가되지 않습니다. 분할 패키지 문제가 발생합니다.

Java8 에서 Java9로 마이그레이션하는 동안 설명 된 상황이 다를 수 있습니다. 여러분의 코드는 이름없는 모듈에 classpath를 설정합니다. 호환성을 극대화하기 위해 이 모듈은 면밀히 조사되지 않았으며 위에 설명 된 모듈 관련 유효성 검사가 모두 적용되지는 않습니다. 그러나 응용 프로그램을 모듈화하기로 결정한 경우 분할 패키지 문제를 해결할 수있는 보편적인 솔루션이 없습니다.

분할 패키지 문제를 해결하기 위해 가장 권장되는 솔루션을 살펴 보겠습니다.

  • 동일한 이름의 패키지를 공유하는 두 개의 JAR 파일이있는 경우 동일한 디렉토리에서 압축을 풀고 전체 디렉토리를 단일 ZIP 파일로 압축하여 단일 JAR 파일을 만들 수 있습니다. 이 방법을 사용하면 모듈 경로에 설정할 수있는 하나의 단일 JAR이 있으며 하나의 자동 모듈만 갖습니다. 이제 패키지가 단일 모듈에 포함되며 분할 패키지는 더 이상 문제가되지 않습니다.
  • 또 다른 방법은 패키지 중 하나의 이름을 바꾸는 것입니다. 성공 확률은 클래스 구조에 따라 다르며, 특히 클래스가 동일한 네임 스페이스에 있거나 존재하지 않는 경우에 따라 다릅니다.
  • 세 번째 옵션은 JAR 중 하나가 궁극적으로 다른 JAR로 대체 될 수 있는지 확인하는 것입니다. JAR을 다른 JAR로 대체 할 기회가 있다면 시도해야합니다.
  • 코드에 적용 할 수있는 마지막 솔루션은 두 모듈의 분할 패키지 문제를 일으키는 전체 패키지를 가져 와서 세 번째 새로운 모듈로 이동시켜 모듈 내에서 필요한 패키지를 내보내는 것입니다.

또한 JEP 200 에서는 "비표준 모듈은 표준 API 패키지를 내보내서는 안됩니다."라는 사실을 기억해야합니다.

제거 된 도구 및 구성 요소

Java 개발자가 코딩을 수행하는 방법에 더 큰 영향을 미치는 또 다른 변경 사항은 rt.jar (런타임), tools.jar및 dt.jar을 제거하는 것 입니다.

이 세 가지 JAR 파일 중 하나를 기반으로 코드 전체에서 가정을 하면 이 이벤트가 앱에 영향을 미칠 수 있습니다. JDK 9에서 ClassLoader::getSystemResource() 메서드를 호출하면 URL이 JAR 파일로 반환되지 않습니다. 대신 유효 기간이 반환됩니다.

getSystemResource() 메소드를 매개 변수 java/lang/Class.class:ClassLoader.getSystemResource("java/lang/Class.class")로 호출 하면 다음 URL이 리턴됩니다. jrt:/java.base/java/lang/Class.class

이러한 새로운 변경 사항을 인식하고 코드가 특정 형식으로이 URL을 수신 할 것으로 예상하는지 확인해야합니다.이 URL은 현재 다를 수 있습니다.

JDK 9에서 제거 된 메소드

Java 9에서는 다음과 같은 메소드가 완전히 제거되었습니다.

  • java.util.logging.LogManager.addPropertyChangeListener
  • java.util.logging.LogManager.removePropertyChangeListener
  • java.util.jar.Pack200.Packer.addPropertyChangeListener
  • java.util.jar.Pack200.Packer.removePropertyChangeListener
  • java.util.jar.Pack200.Unpacker.addPropertyChangeListener
  • java.util.jar.Pack200.Unpacker.removePropertyChangeListener

즉, JDK 9를 사용하는 개발 프로세스 중 이 6 가지 메소드중 어느 것도 사용되지 않도록 해야 합니다. 그렇지 않으면 코드가 컴파일 타임에 중단됩니다.

긍정적인 점은 코드에 포함될 가능성이 너무 낮습니다.

순환 종속성

순환 종속성은 두 개 이상의 모듈 사이의 관계이며 모듈이 서로 직접 또는 간접적으로 종속된다는 사실로 표현됩니다.

Java9에서는 이러한 종류의 모듈 관계가 컴파일 타임이나 링크 타임에 허용되지 않습니다. Jigsaw는 컴파일하는 동안 의도적으로 종속성을 주기적으로 확인하고 두 모듈에 순환 종속성이있는 경우 컴파일이 실패합니다.

그러나 순환 종속성은 모듈 그래프가 이미 해결 된 후에만 런타임 중에 허용됩니다. 런타임 동안 이전에 모듈 그래프가 이미 해결되었으므로 -add-reads 옵션을 사용하여 순환 종속성을 도입 할 수 있습니다 .

순환 종속성을 배제하는 이유는 모듈 시스템을 단순화하거나 모듈 그래프를 더 이해하기 쉽게 만드는 것입니다. 서로 필요한 두 개의 모듈은 단일 모듈로 더 잘 표현됩니다.

개발 프로세스 내에서 순환 종속성은 모듈 간의 연결을 분리하는 인터페이스를 사용하여 해결할 수 있습니다. 이는 서비스 공급자 API를 사용하고 서비스 소비자와 서비스 공급자를 적용하여 구현할 수 있습니다. 그런 다음 모듈은 다른 모듈이 아닌 인터페이스에 의존해야합니다.


개인적인 공부 및 추후 다시 볼 수 있도록 하기 위해 개인 블로그에 번역 내용을 옮겨 놓았습니다.
원문과 내용이 다를시 책임지지 않으며, 저작권 문제가 발생시 언제든 삭제 될 수 있습니다. 

원문보기 : https://www.romexsoft.com/blog/migrate-to-java-9/