본문 바로가기

Java

[Java] 서비스 로더(ServiceLoader) - 소개 및 JAR 파일로 서비스제공자 배포해보기

반응형

서론

ServiceLoader라는 키워드를 처음 들어보고 난 후, 공식문서를 번역해 보면서 공부해봤다. ServiceLoader는 인터페이스와 구현체들로 구성되어 있는데, 런타임 시점에 지연로딩으로 서비스들을 불러오도록 한다.

공식 문서 (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html)

를 읽다보면 흔하게 다음 용어들을 볼 수 있다.

 서비스, 서비스제공자

 

서비스는 Inerface이고, 서비스 제공자는 Implement class 이다. 

어떻게 ServiceLoader가 지연로딩으로 구현체들을 가져올수 있고, 또 어떻게 구현체들을 등록할 수 있는걸까?

 

아래 내용들은 공식문서 내용을 번역하고 GPT에게 맡겨 재정리한 내용들이다.


 

본론

서비스(interface)의 기본 구조

  1. 서비스는 보통 인터페이스나 추상 클래스로 만듭니다
  2. 일반 클래스로도 만들 수 있지만, 권장하지 않아요
  3. 누구나 접근할 수 있어야 합니다 (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)에 지켜야할 규칙들과 배포 방식들이 존재합나디.

 

기본 규칙

  1. 서비스 제공자는 일반적으로 구체적인 클래스입니다
  2. public으로 선언되어야 합니다
  3. 내부 클래스(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. 서비스 제공자 클래스 작성

구현체1
구현체2

 

2. 서비스 제공자 구성 파일 생성:

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

 

주의점 

구성 파일은 UTF-8로 인코딩되어야 합니다. 다른 인코딩을 사용하면 ServiceLoader가 제공자를 제대로 인식하지 못할 수 있습니다.

findFirst()를 실행 했을 때, 첫번째 줄의 서비스 제공자를 찾아서 가져온다는 점을 유의해야 합니다.

 

 

현재 디렉토리 구조

 

3. 서비스 로더를 통한 서비스 제공자 로드:

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

실행 코드
실행 결과, 서비스 제공자를 성공적으로 가져왔다.

 

 

특징 : ServiceLoader의 제공자 발견 타이밍과 캐싱

ServiceLoader는 지연 로드(Lazy Loading) 방식을 사용하여 서비스 제공자를 로드하고 인스턴스화합니다. 또한, 한 번 로드된 제공자는 캐시에 저장되어 재사용됩니다.

제공자 발견 과정

  1. 초기화:    ServiceLoader 객체가 생성되면, 아직 제공자는 로드되지 않습니다.
  2. 첫 번째 접근: iterator()나 stream() 메서드를 호출하면, 제공자가 로드되고 인스턴스화됩니다. 이때, 구성 파일에 명시된 제공자 클래스들이 순차적으로 인스턴스화됩니다.
  3. 캐시 유지: 제공자 인스턴스는 캐시에 저장됩니다. 이후 동일한 ServiceLoader 객체에서 다시 제공자를 요청하면, 캐시된 인스턴스를 반환합니다.
  4. 재로딩: 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

 

반응형