티스토리 뷰
목차
배경
자바 기본 개념을 공부하던 중에 Java 9 부터 제공되는 모듈에 대한 소개가 인상깊어 기록해두었다. 실무에서 사용되는 프로젝트를 보면 규모가 커지면서 협업과 유지보수에 어려움을 겪게되는 경우가 많은데, 이러한 상황에서 프로젝트를 모듈화하여 구성한다면 협업과 유지보수를 쉽게 하고 더 나아가 모듈을 통한 재사용성도 향상시킬 수 있다는 생각이 들어 많은 도움이 될 것 같아 블로그에 추가로 작성하였다.
모듈
들어가기에 앞서, 모듈에 대해 간략하게 먼저 작성하는게 좋을 것 같다. 모듈은 java 9 부터 지원하는 기능이며 패키지 관리 기능까지 포함된 라이브러리이다. 일반적인 라이브러리는 외부 프로그램에서 라이브러리 내부에 포함된 모든 패키지에 접근할 수 있지만, 모듈은 일부 패키지를 은닉하여 접근할 수 없는 기능을 제공한다.
라이브러리와 모듈의 공통점
모듈도 라이브러리 이므로 *.jar 형태로 배포할 수 있으며, 외부 프로그램에서 모듈이 필요할 경우 라이브러리를 사용하던 것처럼 *.jar 파일을 다운로드해서 이용하면 된다.
라이브러리와 모듈의 차이점
라이브러리는 외부 프로그램에서 라이브러리 내부에 포함된 모든 패키지에 접근할 수 있지만, 모듈은 일부 패키지를 은닉하여 외부 프로그램에서 접근할 수 없게 할 수 있다. 또한, 모듈은 자신이 실행할 때 필요로 하는 의존 모듈을 모듈 기술자(module-info.java)에 기술하여, 모듈간 의존관계를 쉽게 파악할 수 있도록 도와준다.
이처럼 모듈별로 개발하고 조립하는 방식을 사용하면 재사용성 및 유지보수에 유리하기 때문에, 규모가 큰 프로젝트일수록 기능별로 모듈화(modulization)해서 개발할 수 있다.
라이브러리와 모듈의 가장 큰 차이는 모듈은 은닉을 제공한다는 점인데, 이는 다음과 같은 목적으로 사용 할 수 있다.
- 모듈 사용 방법 통일
- 모듈 외부에서 사용할 패키지만 exports로 드러내고 그 외 사용하지 않을 패키지는 은닉하여, 사용 방법을 통일할 수 있다. (interface로 외부 클래스에서 사용 방법을 통일했다는 개념을 프로젝트 단위로 확장시키면 이해하기 편할 것이다.)
- 쉬운 수정 및 유지보수 용이
- 모듈 사용 방법을 통일함으로써 수정과 유지보수를 용이하게 할 수 있다.
- 모듈 성능 향상 혹은 코드 개선 등을 위해서 은닉한 패키지만을 수정하고, 사용 방법을 통일하기 위해 exports로 드러낸 패키지를 수정하지 않는다면 모듈 외부에 영향을 주지 않고 코드를 수정할 수 있다.
응용 프로그램 모듈화
응용 프로그램은 하나의 프로젝트로도 개발할 수 있지만, 이것을 기능별로 서브 프로젝트(모듈)로 쪼갠 다음 조합하여 개발할 수 있다. 예를 들어, Java로 작성된 my_application_2 응용 프로그램은 2개의 서브 프로그램(모듈)인 my_module_a와 my_module_b로 쪼개서 개발하고 이들을 조합하여 완성할 수 있다. 실무에서 사용되는 프로젝트를 보면 응용 프로그램의 규모가 커질수록 협업과 유지보수 측면에서 어려움을 겪게되는 프로젝트가 많은데, 이럴 경우 모듈로 쪼개어 개발하는 것이 해결책 중 (혹은 일시적인 보완책) 하나가 될 수 있고, 이렇게 개발된 모듈은 재사용성을 높여 다른 응용 프로젝트에서 재사용도 가능하다.
응용프로그램과 모듈
응용프로그램(자주 쓰이는 하나의 java 프로젝트라고 이해하였다.)도 하나의 모듈이다. 모듈을 개발하는 프로젝트가 아니라 이와 같이 다른 모듈을 조합하는 응용프로그램을 위한 프로젝트도 모듈기술자(modules-info.java)가 필요하다.
modules-info.java은 어떠한 모듈을 가져와 사용할 것인지 기술하는 역할을 하며, 일반적으로 모듈기술자(modules-info.java)가 포함된 프로젝트를 모듈이라고 한다.
응용프로그램 모듈화하여 구성하기
모든 과정은 Mac OS + java 17 + 인텔리제이 IDE 로 진행하였다.
모듈 생성(모듈 프로젝트의 패키지 및 클래스 생성)
다음과 같이 패키지와 클래스를 생성하였다. 나는 두개의 모듈을 사용하는 프로젝트를 만들어 보기 위해 thisIsJava_module_a와 thisIsJava_module_b 모듈 프로젝트를 구성하였다.
- thisIsJava_module_a
package package1;
public class A {
// 메서드 선언
public void method(){
System.out.println("thisIsJava_module_a package1 A class method 실행");
}
}
package package2;
public class B {
// 메서드 선언
public void method(){
System.out.println("thisIsJava_module_a package2 B class method 실행");
}
}
- thisIsJava_module_b
package package3;
public class C {
// 메서드 선언
public void method(){
System.out.println("thisIsJava_module_b package2 C class method 실행");
}
}
package package4;
public class D {
// 메서드 선언
public void method(){
System.out.println("thisIsJava_module_b package2 D class method 실행");
}
}
패키지 경로는 다음과 같다.
그 후, 각 모듈 프로젝트에 모듈 기술자(module-info.java)를 다음과 같이 작성하였다. 이 때 exports 키워드를 통해 모듈이 가지고 있는 어떤 패키지를 외부에 노출할지 기술한다. 이때 module-info.java는 프로젝트 최상단인 src → main → java 패키지 하위에 생성하였다.
- thisIsJava_module_a
module thisIsJava.module.a {
exports package1;
exports package2;
}
- thisIsJava_module_b
module thisIsJava.module.b {
exports package3;
exports package4;
}
외부 응용프로그램(외부 프로젝트)에서 모듈 사용
응용프로그램에서 사용할 모듈의 경로를 다음과 같은 순서로 지정하였다.
1. 인텔리제이 상단 File 탭 → Project Structure 선택
2. 좌측 Project Settings 중 Modules 탭 클릭 → + 버튼 선택 및 New Module 선택
3. import할 모듈의 프로젝트 선택 및 Open
4. 사용할 모듈의 경로 지정 후 모듈 이름 지정
이 때, module Name에 하이픈(-)이 들어가면 안된다. (java 모듈 시스템 네이밍 규칙)
5. 사용할 모듈에 대해 2~4번 과정을 반복한다. 나는 여러개의 모듈을 사용해보고 싶어 thisIsJava_module_a와 thisIsJava_module_b 모듈에 대해 반복하였다.
혹은 집합 모듈을 만들어 사용할 모듈을 한번에 세팅해줄 수도 있다. 집합 모듈은 여러 모듈을 모아놓은 모듈이며 자체적인 패키지를 가지지 않고 모듈 기술자에 전이 의존 설정만 한다.
예를 들어 my_module_a와 my_module_b를 제공하는 집합모듈 my_module의 모듈 기술자는 다음과 같이 작성할 수 있다. (다른 프로젝트에서는 my_module만 requires하면 my_module_a 모듈과 my_module_b 모듈을 사용할 수 있다.)
module my_module {
requires transitive my_module_a;
requires transitive my_module_b;
}
6. 5번까지 진행하고 나면 프로젝트가 사용중인 module 목록에 thisIsJava_module_a와 thisIsJava_module_b가 추가되고 해당 모듈의 패키지 경로를 확인할 수 있다.
7. 프로젝트 패키지 경로에서도 module이 추가된 것을 볼 수 있다.
사용할 모듈을 지정한 후 응용 프로그램에서도 모듈 기술자 modules-info.java를 작성한다. 이 때 모듈을 내보내는 프로젝트에서는 exports 키워드로 패키지를 지정했다면, 이 모듈을 사용할 프로젝트에서는 requires 키워드를 사용하여 어떤 외부 모듈을 사용할지 기술한다. 응용프로그램에서의 modules-info.java 또한 프로젝트 최상단인 src → main → java 패키지 하위에 생성하였다.
module thisIsJava {
// Project Structure 에서 지정한 모듈 Name을 requires
requires thisIsJava.module.a;
requires thisIsJava.module.b;
}
그 후 pom.xml (빌드도구로 Gradle을 사용한다면 build.gradle)에 외부 모듈에 대한 의존성을 추가해주었다.
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>thisIsJava_module_a</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>thisIsJava_module_b</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
외부 응용프로그램(외부 프로젝트)에서 모듈 호출
다음과 같은 코드로 응용프로그램에서 모듈의 메서드를 호출하였다.
package module
import package1.A;
import package2.B;
import package3.C;
public class Main {
public static void main(String[] args) {
// thisIsJava_module_a 모듈에 포함된 A 클래스의 method 호출
A a = new A();
a.method();
// thisIsJava_module_a 모듈에 포함된 B 클래스의 method 호출
B b = new B();
b.method();
// thisIsJava_module_b 모듈에 포함된 C 클래스의 method 호출
C c = new C();
c.method();
}
}
다음과 같이 잘 호출되는 것을 볼 수 있다.
출처
- 이것이 자바다
'Java & Kotlin' 카테고리의 다른 글
[Java] 적절한 예외 처리(Exception Handling) 방안 (3) | 2023.12.08 |
---|---|
[Java] 예외와 예외 처리 (1) | 2023.12.02 |
[Java] 추상 클래스와 인터페이스의 개념과 차이점 (2) | 2023.11.15 |
[Java] 클래스 로더(Class Loader)의 기능과 역할 (2) | 2023.11.08 |
[Java] JVM 구조와 메모리 사용영역, Java 쓰레드와 메모리 구조 (1) | 2023.11.05 |