서론
ServiceLoader라는 키워드를 처음 들어보고 난 후, 공식문서를 번역해 보면서 공부해봤다. ServiceLoader는 인터페이스와 구현체들로 구성되어 있는데, 런타임 시점에 지연로딩으로 서비스들을 불러오도록 한다.
공식 문서 (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html)
를 읽다보면 흔하게 다음 용어들을 볼 수 있다.
서비스, 서비스제공자
서비스는 Inerface이고, 서비스 제공자는 Implement class 이다.
어떻게 ServiceLoader가 지연로딩으로 구현체들을 가져올수 있고, 또 어떻게 구현체들을 등록할 수 있는걸까?
아래 내용들은 공식문서 내용을 번역하고 GPT에게 맡겨 재정리한 내용들이다.
본론
서비스(interface)의 기본 구조
- 서비스는 보통 인터페이스나 추상 클래스로 만듭니다
- 일반 클래스로도 만들 수 있지만, 권장하지 않아요
- 누구나 접근할 수 있어야 합니다 (public)
서비스를 만들 때 지켜야 할 두 가지 중요한 원칙이 있습니다:
1. 충분한 정보 제공을 위한 메소드 설계
- 서비스는 구현체(Provider)의 특징을 파악할 수 있는 충분한 메소드를 가져야 합니다.
- 예시: 결제 서비스라면
public interface PaymentService {
String getProviderName(); // 결제사 이름
boolean supportsInstallment(); // 할부 가능 여부
double getFee(); // 수수료율
}
2. 직접 구현과 간접 구현의 구분
- 서비스가 "직접 구현체"인지 "팩토리"인지 이름으로 명확히 표현해야 합니다.
- 복잡하거나 생성 비용이 큰 객체는 팩토리 패턴을 사용합니다.
- 예시
// 직접 구현 (간단한 경우)
public interface SimpleLogger {
void log(String message);
}
// 간접 구현 (복잡한 경우) - 어미에 Factory가 붙는다.
public interface DatabaseConnectionFactory {
Connection createConnection(String config);
}
서비스 제공자
서비스 제공자(Service Provider)에 지켜야할 규칙들과 배포 방식들이 존재합나디.
기본 규칙
- 서비스 제공자는 일반적으로 구체적인 클래스입니다
- public으로 선언되어야 합니다
- 내부 클래스(inner class)는 될 수 없습니다
배포 방식
배포 방식은 2가지 있습니다.
1. 모듈 방식 (Java 9+)
// module-info.java
module my.provider {
provides PaymentService with KakaoPayProvider;
}
2. JAR 파일 방식 (전통적인 방식)
META-INF/services/com.example.PaymentService
서비스 제공자 생성 방식
서비스 제공자의 인스턴스화 방법은 두 가지입니다:
1. provider() 메소드 사용
public class KakaoPayProvider {
// provider() 메소드로 인스턴스 생성 제어
public static PaymentService provider() {
return new KakaoPayImplementation();
}
}
2. 기본 생성자 사용
public class NaverPayProvider implements PaymentService {
// 매개변수 없는 public 생성자
public NaverPayProvider() {
// 초기화 로직
}
}
실습
한번 클래스 경로에 JAR 파일로 서비스 제공자를 배포해봤습니다.
0. 서비스 작성

1. 서비스 제공자 클래스 작성


2. 서비스 제공자 구성 파일 생성:
- 파일 경로: META-INF/services/item1.DiscountPolicyFactory
- 파일 내용: 각 줄은 서비스 제공자의 전체 클래스 이름을 나열합니다.

주의점
구성 파일은 UTF-8로 인코딩되어야 합니다. 다른 인코딩을 사용하면 ServiceLoader가 제공자를 제대로 인식하지 못할 수 있습니다.
findFirst()를 실행 했을 때, 첫번째 줄의 서비스 제공자를 찾아서 가져온다는 점을 유의해야 합니다.

3. 서비스 로더를 통한 서비스 제공자 로드:
애플리케이션은 ServiceLoader를 사용하여 DiscountPolicyFactory 서비스를 로드하고, 구성 파일에 명시된 제공자를 자동으로 인스턴스화합니다.


특징 : ServiceLoader의 제공자 발견 타이밍과 캐싱
ServiceLoader는 지연 로드(Lazy Loading) 방식을 사용하여 서비스 제공자를 로드하고 인스턴스화합니다. 또한, 한 번 로드된 제공자는 캐시에 저장되어 재사용됩니다.
제공자 발견 과정
- 초기화: ServiceLoader 객체가 생성되면, 아직 제공자는 로드되지 않습니다.
- 첫 번째 접근: iterator()나 stream() 메서드를 호출하면, 제공자가 로드되고 인스턴스화됩니다. 이때, 구성 파일에 명시된 제공자 클래스들이 순차적으로 인스턴스화됩니다.
- 캐시 유지: 제공자 인스턴스는 캐시에 저장됩니다. 이후 동일한 ServiceLoader 객체에서 다시 제공자를 요청하면, 캐시된 인스턴스를 반환합니다.
- 재로딩: reload() 메서드를 호출하면, 기존 캐시가 지워지고, 다시 제공자가 로드됩니다. 이를 통해 새로운 서비스 제공자를 동적으로 추가할 수 있습니다.
정리하기
| 내용 | 세부 내용 |
| 서비스와 서비스 제공자 | 서비스: 인터페이스나 추상 클래스로 정의. 서비스 제공자: 해당 서비스를 구현한 구체적인 클래스. |
| 배포 방법 | 1. 모듈 시스템 사용 시: module-info.java에 provides 지시문 사용. 2. 클래스 경로에 JAR 파일로 배포 시: META-INF/services 디렉토리에 구성 파일 생성. |
| 제공자 메서드 vs. 생성자 | 1. 제공자 메서드: 복잡한 인스턴스 생성 로직을 캡슐화할 수 있지만, 자동 모듈에서는 지원되지 않음. 2. 생성자: 단순하게 매개변수가 없는 공개 생성자를 통해 인스턴스화. |
| 자동 모듈 제한 사항 | 자동 모듈로 배포된 서비스 제공자는 제공자 메서드를 사용할 수 없으며, 매개변수가 없는 공개 생성자만 허용. |
| ServiceLoader의 동작 | - 지연 로드: 필요할 때만 제공자를 로드. - 캐싱: 한 번 로드된 제공자는 캐시에 저장되어 재사용. - reload(): 캐시를 지우고 다시 제공자를 로드. |
참고자료
chatGPT o1 모델
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html
https://stackoverflow.com/questions/58140651/java-spi-choosing-a-single-implementation
'Java' 카테고리의 다른 글
| 중복 데이터 검증 INSERT. MySQL INSERT IGNORE 분석 (0) | 2025.04.04 |
|---|---|
| [Java] 불변 객체란? 직관적으로 이해하는 불변 객체 (3) | 2025.01.03 |