새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
자바 9 학습
오늘은 자바 9의 변경 사항과 자바 11에 대해서 학습을 정리하려고합니다.
그 외 추가로 학습 시간이 남는다면 git flow
정책도 한번 정리해보겠습니다.
자바 feature 학습
자바 모듈 접근 제어 기능
자바 8 버전에서는 private
접근 제어자를 사용해도 리플렉션 옵션 기능을 통해 수정이 가능했습니다.
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
PrivateFieldObject fieldObject = new PrivateFieldObject();
Class<? extends PrivateFieldObject> objectClass = fieldObject.getClass();
// 기본 접근제어자는 수정가능
Field shallow = objectClass.getDeclaredField("shallow");
shallow.set(fieldObject,"public 접근제어자는 바로 수정가능");
// private는 별도의 설정이 필요합니다.
Field deep = objectClass.getDeclaredField("deep");
deep.setAccessible(true);
deep.set(fieldObject,"private은 access로 접근하여 deep 리플렉션");
System.out.println("fieldObject = " + fieldObject);
// 리플렉션을 사용하는 이유는 동적으로 클래스를 생성하고 초기화하고 메서드를 호출 할 수 있습니다.
}
자바 9 버전에서는 리플렉션으로 설정이 불가능합니다.
리플렉션으로 수정이 가능한 방법은 module-info.java 명령어
,VM 옵션 추가 가 있습니다.
용어 정리
- deep reflection :
private
접근 제어자까지 접근하는 리플렉션 - shallow reflection :
public
접근 제어자까지만 접근하는 리플렉션
open
해당 모듈은 리플렉션으로 private
이 수정 가능합니다.
open module com.domain {
export com.study;
}
com.domain
모듈내에 export
로 선언한 패키지는 기존처럼 private
수정이 가능합니다.
opens
특정 패키지에 대한 deep reflection 허가
module com.domain {
export com.study;
opens com.study;
export com.library;
}
이런 경우 com.study 패키지 내에 있는 클래스만 deep reflection
이 허용 됩니다.
opens to
module com.domain {
export com.study;
opens com.study to com.api;
export com.library;
}
이런 경우 com.study 패키지의 deep reflection
권한은 com.api
모듈 내에서만 허용됩니다.
서비스 로더 변화
기존 서비스 로더를 통해서 의존성 주입이 가능했었습니다.
public class Main {
public static void main(String[] args) {
for (GreetingService greetingService : ServiceLoader.load(GreetingService.class)) {
greetingService.greet();
}
}
}
resources/META-INF/services/(interface)
- 인터페이스를 등록한 뒤 파일 내부에는 구현체의 풀네임을 작성합니다.
- ServiceLoader.load()를 통해서 구현체를 가져올 수 있습니다.
자바 9 이후에는 module-info.java
를 통해서 서비스 로더를 사용할 수 있습니다. 다른 모듈에 있는 서비스를 사용할 수 있습니다.
uses 사용측
module com.api {
requires com.domain;
uses service.StringRepository;
}
provides 서비스 제공 측
module com.admin {
exports service; // 서비스 패키지를 공유합니다.
provides service.StringRepository
with DatabaseStringRepository, MemoryStringRepository;
}
provides
는 인터페이스를 등록하며 구현체는 with
절 뒤에 ,
를 구분차로 추가하여 등록할 수 있습니다.
스프링 부트에서 사용하던 @Controller, @Service 처럼 다른 패키지에 위치하고 있는 각각의 기능을 인터페이스만 의존하여 확장에 자유로운 객체지향 코드로 작성할 수 있습니다.
public class SpringRepositoryLoader {
/**
* 외부 설정파일을 변경하는 것으로 구현체가 코드 변경없이 컴포넌트 교체가 가능합니다.
*/
private static final String DEFAULT_REPOSITORY = "service.DatabaseStringRepository";
public static StringRepository getDefault() {
return getRepository(DEFAULT_REPOSITORY);
}
public static StringRepository getRepository(String name) {
/**
* 자바에서 제공하는 서비스 로더에 대한 구현체를 가져오고
* 특정 조건을 만족하면 직접 임포트하지 않고 구현체를 선택해서 사용할 수 있다.
*/
ServiceLoader<StringRepository> load = ServiceLoader.load(StringRepository.class);
for (StringRepository stringRepository : load) {
if (stringRepository.getClass().getName().equals(name)) {
return stringRepository;
}
}
throw new IllegalArgumentException("원하는 서비스를 찾을 수 없다.");
}
}
서비스를 사용하는 컴포넌트는 SpringRepositoryLoader
를 의존하여 어떤 구현체가 오든 상관없이 코드를 작성할 수 있습니다.
public class StringSaveComponent {
private final StringRepository stringRepository = SpringRepositoryLoader.getDefault();
public void mainLogic() {
stringRepository.save("TIL 작성 저장중");
}
}
public static StringRepository getDefault() {
Properties properties = new Properties();
try {
// properties 파일을 로드함
properties.load(Files.newBufferedReader(Paths.get("con-api/src/main/resources/config.properties")));
String property = properties.getProperty("database.type");
if (property.equals("DB")) {
return getRepository(DB);
} else if (property.equals("MEMORY")) {
return getRepository(MEMORY);
} else {
throw new IllegalArgumentException("없어");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//config.properties
database.type=DB
프로퍼티를 사용해서 동적으로 변경도 가능합니다.
자바 9 그외 변경점
클래스 로더의 구성 변경 - 자바 9
클래스 로더는 JVM이 사용하는 파일을 알지 못해도 필요한 클래스만 메모리에 로드하는 역할을 합니다. 기존 자바 8에서는 부트스트랩 클래스 로더는 런타임 환경에서 JRE
에 사용되는 모든 핵심 클래스를 읽어야했습니다. 불필요한 클래스까지 모두 로드하게 만들게 되었고 필요로 하지 않는 클래스도 로드되는 문제가 있었습니다.
또한, 모든 클래스가 평면적인 클래스 패스에 배치되기 때문에, 모듈 간의 명확한 경계가 없었습니다. 이는 클래스 간의 의존성 관계를 효율적으로 관리하기 어려웠습니다.
평면적인 클래스 패스란
클래스패스: - app/ - lib/libraryA.jar - lib/libraryB.jar
자바 애플리케이션에서 클래스 로딩 시, 클래스 파일들이 하나의 연속된 네임스페이스(클래스 패스)에 배치되어 있는 구조를 말합니다.
이 구조에서는 클래스 로더가 클래스 패스에 지정된 모든 경로를 단순히 순차적으로 검색하여 클래스를 로드합니다.이러한 방식은 모듈 간의 명확한 경계가 없고, 클래스 충돌 및 보안 문제가 발생할 수 있습니다.
- Bootstrap 클래스 로더
java.base 모듈의 클래스들을 가져옵니다. 로드하는 클래스의 범위가 축소되었습니다. - Platform 클래스 로더(
Extensions 클래스 로더)
확장 개념이였던 특정 폴더가 아닌 부트 스트랩 클래스 로더가 처리하지 않은 SE or JCP 모듈의 클래스들을 가져옵니다. - Application 클래스 로더
Java SE Platform 혹은 JDK 안에 있지 않은 클래스들을 가져옵니다.
JRE/JDK 구성 변경
rt.jar,tool.jar 등 JRE/JDK 내부에 있던 몇 가지 파일 삭제 JRE/JDK 디렉토리 구성도 변경되었습니다.
- 변경된 이유는 모듈 시스템 도입으로 JAR 파일 하나에 담기지 않고 모듈로 나누어 처리하도록 하기 위함입니다.
정리
자바 9에서 모듈 기능 추가로 많은 변화가 있었습니다.
클래스 로더의 변화로 위임의 범위가 명확해졌으며, 모듈간 의존성과 캡슐화로 유지보수 및 필요한 모듈만 사용할 수 있게 되어 불필요한 기본 라이브러리도 사용하지 않을 수 있습니다.
댓글남기기