자바 9 - 자바 플랫폼 모듈 시스템 (JPMS) (vs 빌드툴 Module)
- 자바 9에서는 모듈 시스템이 등장
- 모듈 (Module) : 여러 코드가 모인 독립적인 구성요소
- 비슷한 역할을 하는 코드끼리 모아 유지보수성 높일 수 있음 / 역할 간 의존성 제어 = 안전성 증대
- 자바 9 이전에도 Maven, Gradle과 같은 빌드툴을 활용해 모듈 시스템이 구성되어 있긴 했음
- 한 프로젝트 내에 여러 모듈을 만들어 의존성 관리 (멀티모듈)
- JPMS vs 빌드툴 Module?
- JPMS는 빌드툴 없이 모듈 구성이 가능
- 이를 통해 JDK에서 필요한 일부 코드(모듈)만 다운로드 가능 (성능,용량 측면 유지보수성 증대, Modular run time image)
- JPMS와 빌드툴 Module 함께 사용 가능
- JPMS는 특정 패키지 기준으로 특정 모듈에 open 가능 / private member에 대한 reflection open 여부 설정 가능
JPMS - 모듈 생성 및 특정 패키지 기준 open
- src/main/java 폴더에 module-info.java 를 추가하면 해당 모듈은 JPMS로 간주 (named module)
- module-info.java가 없는 모듈은 unnamed module
- 특정 패키지에 대한 의존성을 열어주기 위해서는 module-info.java에 exports {패키지 경로}를 명시
- 특정 모듈에만 의존성을 열어주고 싶다면 to {모듈명} 명시
module com.domain {
exports open.domain;
exports open.domain to com.api; // api 모듈에만 의존성 open
}
- 모듈에 대한 의존성을 추가하기 위해서는 module-info.java에 requires {static | transitive} 모듈명 명시
- static : 컴파일 타임 의존성만 추가 (리플렉션 등의 런타임 의존성 X, 오직 코드에 작성된 의존성)
- transitive : 추이적 의존성 (B->C 의존성 추가 / A->B 의존성 추가 시 A->C 의존성도 전이됨)
- X : 컴파일 + 런타임 의존성 추가
module com.admin {
requires com.domain;
}
JPMS private member reflection open 여부 설정
- module-info.java에 open 키워드로 Deep Reflection 여부 설정 가능
- Deep Reflection : private 멤버에 대한 리플렉션
- Shallow Reflection : public 멤버에 대한 리플렉션 (private X)
// 모듈 전체에 대해 Deep Reflection open
open module com.domain {
exports open.domain;
}
// open.domain 패키지에 대한 Deep Reflection open
module com.domain {
opens open.domain;
}
// open.domain 패키지에 대해 com.api 모듈만 Deep Reflection open
module com.domain {
opens open.domain to com.api;
}
- exports 없이 open만 할 경우 직접적인 참조는 불가능하지만 Class.forName(com.domain.Person) 활용해 클래스를 가져와 Deep Reflection 가능
- exports만 할 경우 Shallow Reflection은 가능
JPMS - 서비스
- 코드의 변경 없이 기능을 갈아끼울 수 있는 매커니즘 (개념적으로 DI와 동일)
- Service Provider가 Service Consumer에게 의존성(구현체)을 주입하는 방식
- Service Loader에서 여러 구현체 중 주입할 구현체를 선택
// domain 모듈
// 인터페이스
public interface StringRepository {
void save(String newStr);
}
// 구현체 1
public class MemoryStringRepository implements StringRepository {
private final List<String> strings = new ArrayList<>();
@Override
public void save(String newStr) {
strings.add(newStr);
System.out.println("문자열 메모리 저장");
}
}
// 구현체 2
public class DatabaseStringRepository implements StringRepository {
@Override
public void save(String newStr) {
System.out.println("DB 메모리 저장");
}
}
// domain 모듈 module-info.java
open module com.domain {
exports open.domain;
exports org.domain.service;
// 서비스 프로바이더에 구현체 등록
provides org.domain.service.StringRepository with
org.domain.service.MemoryStringRepository,
org.domain.service.DatabaseStringRepository;
}
// api 모듈
module com.api {
requires com.domain;
uses org.domain.service.StringRepository; // StringRepository 사용
}
public class StringRepositoryLoader {
// 외부 config 파일 등을 import해 외부에서 변경 가능
public static final String DEFAULT = "org.domain.service.DatabaseStringRepository";
public static StringRepository getDefaultRepository() {
return getRepository(DEFAULT);
}
private static StringRepository getRepository(String name) {
// ServiceLoader에서 해당 Interface에 해당하는 구현체 찾음
for (StringRepository repository : ServiceLoader.load(StringRepository.class)) {
if (repository.getClass().getName().equals(name)) {
return repository;
}
}
throw new IllegalArgumentException("Repository Not Found!");
}
}
public class StringSaveConsumer {
// Service Consumer는 외부에서 주입받아 그대로 사용
private final StringRepository stringRepository = StringRepositoryLoader.getDefaultRepository();
public void consume() {
stringRepository.save("test");
}
}
Reference
'Java' 카테고리의 다른 글
[Java 9] 주요 변경내용 : Collection / Optional / Stream 관련 기능, 내부 문자열 처리방식, G1 GC (0) | 2024.05.18 |
---|---|
[Java 9] 언어적 변경 : try-with-resources, @SafeVarargs, inner class + diamond Syntax, Interface + private method (0) | 2024.05.17 |
인터페이스의 default method와 다이아몬드 문제 (0) | 2023.06.19 |
참조변수와 인스턴스에 따른 method, 변수, static method 참조 결과 비교 (0) | 2023.06.19 |
[Effective Java] Item20. 추상 클래스보다는 인터페이스를 우선하라 (0) | 2022.06.16 |