1. 개요

Java 6에는 지정된 인터페이스와 일치하는 구현을 검색하고 로드하는 기능인 SPI(서비스 공급자 인터페이스)가 도입되었습니다.

이 사용방법(예제)에서는 Java SPI의 구성 요소를 소개하고 실제 사용 사례에 적용하는 방법을 보여줍니다.

2. Java SPI의 용어 및 정의

Java SPI는 네 가지 주요 구성 요소를 정의합니다.

2.1. 서비스

일부 특정 응용 프로그램 기능 또는 기능에 대한 액세스를 제공하는 잘 알려진 프로그래밍 인터페이스 및 클래스 집합입니다.

2.2. 서비스 제공자 인터페이스

서비스에 대한 프록시 또는 끝점 역할을 하는 인터페이스 또는 추상 클래스입니다.

서비스가 하나의 인터페이스인 경우 서비스 공급자 인터페이스와 동일합니다.

서비스와 SPI는 함께 Java 생태계에서 API로 잘 알려져 있습니다.

2.3. 서비스 제공자

SPI의 특정 구현. 서비스 제공자는 서비스 유형을 구현하거나 확장하는 하나 이상의 구체적인 클래스를 포함합니다.

서비스 제공자는 META-INF/services 리소스 디렉토리에 있는 제공자 구성 파일을 통해 구성 및 식별됩니다 . 파일 이름은 SPI의 완전한 이름이고 그 내용은 SPI 구현의 완전한 이름입니다.

서비스 공급자는 확장, 응용 프로그램 클래스 경로에 배치하는 jar 파일, Java 확장 클래스 경로 또는 사용자 정의 클래스 경로의 형태로 설치됩니다.

2.4. 서비스로더

SPI의 핵심은 ServiceLoader 클래스입니다. 이것은 구현을 느리게 발견하고 로드하는 역할을 합니다. 컨텍스트 클래스 경로를 사용하여 공급자 구현을 찾고 내부 캐시에 넣습니다.

3. 자바 생태계의 SPI 샘플

Java는 많은 SPI를 제공합니다. 다음은 서비스 제공자 인터페이스와 제공하는 서비스의 샘플입니다.

4. 쇼케이스: 환율 애플리케이션

이제 기본 사항을 이해했으므로 환율 적용을 설정하는 데 필요한 단계를 설명하겠습니다.

이러한 단계를 강조하려면 exchange-rate-api , exchange-rate-implexchange-rate-app의 세 가지 프로젝트 이상을 사용해야 합니다.

하위 섹션 4.1. 에서 모듈 exchange-rate-api 를 통해 Service , SPI  및 ServiceLoader 를 다룬 다음 하위 섹션 4.2에서 다룰 것입니다. exchange-rate-impl 모듈에서 서비스 제공자구현 하고 마지막으로 exchange-rate-app 모듈을 통해 하위 섹션 4.3에서 모든 것을 통합 합니다 .

사실, 우리는 서비스 제공자 에게 필요한 만큼 의 모듈을 제공할 수 있고 모듈 exchange-rate-app 의 클래스 경로에서 사용할 수 있도록 만들 수 있습니다 .

4.1. API 구축

exchange-rate-api 라는 Maven 프로젝트를 만드는 것으로 시작합니다 . 이름이 api 라는 용어로 끝나는 것은 좋은 습관 이지만 무엇이든 부를 수 있습니다.

그런 다음 환율 통화를 나타내는 모델 클래스를 만듭니다.

package com.baeldung.rate.api;

public class Quote {
    private String currency;
    private LocalDate date;
    ...
}

그런 다음 QuoteManager 인터페이스를 생성하여 견적 검색을 위한 서비스 를 정의합니다.

package com.baeldung.rate.api

public interface QuoteManager {
    List<Quote> getQuotes(String baseCurrency, LocalDate date);
}

다음으로 서비스에 대한 SPI 를 만듭니다 .

package com.baeldung.rate.spi;

public interface ExchangeRateProvider {
    QuoteManager create();
}

마지막 으로 클라이언트 코드에서 사용할 수 있는 유틸리티 클래스 ExchangeRate.java 를 만들어야 합니다 . 이 클래스는 ServiceLoader 에 위임합니다 .

먼저 정적 팩토리 메서드 load() 를 호출하여 ServiceLoader 의 인스턴스를 가져옵니다 .

ServiceLoader<ExchangeRateProvider> loader = ServiceLoader .load(ExchangeRateProvider.class);

그런 다음 iterate() 메서드를 호출하여 사용 가능한 모든 구현을 검색하고 검색합니다.

Iterator<ExchangeRateProvider> = loader.iterator();

검색 결과가 캐시되므로 새로 설치된 구현을 검색하기 위해 ServiceLoader.reload() 메서드를 호출할 수 있습니다 .

Iterator<ExchangeRateProvider> = loader.reload();

다음은 유틸리티 클래스입니다.

public class ExchangeRate {

    ServiceLoader<ExchangeRateProvider> loader = ServiceLoader
      .load(ExchangeRateProvider.class);
 
    public Iterator<ExchangeRateProvider> providers(boolean refresh) {
        if (refresh) {
            loader.reload();
        }
        return loader.iterator();
    }
}

이제 설치된 모든 구현을 가져오는 서비스가 있으므로 클라이언트 코드에서 모든 구현을 사용하여 애플리케이션을 확장하거나 선호하는 구현을 선택하여 하나만 사용할 수 있습니다.

이 유틸리티 클래스는 api 프로젝트 의 일부일 필요가 없습니다 . 클라이언트 코드는 ServiceLoader 메서드 자체 를 호출하도록 선택할 수 있습니다 .

4.2. 서비스 제공자 구축

이제 exchange-rate-impl 이라는 Maven 프로젝트를 만들고 pom.xml 에 API 의존성을 추가하겠습니다 .

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

그런 다음 SPI를 구현하는 클래스를 만듭니다.

public class YahooFinanceExchangeRateProvider 
  implements ExchangeRateProvider {
 
    @Override
    public QuoteManager create() {
        return new YahooQuoteManagerImpl();
    }
}

다음은 QuoteManager 인터페이스 의 구현입니다 .

public class YahooQuoteManagerImpl implements QuoteManager {

    @Override
    public List<Quote> getQuotes(String baseCurrency, LocalDate date) {
        // fetch from Yahoo API
    }
}

검색을 위해 제공자 구성 파일을 생성합니다.

META-INF/services/com.baeldung.rate.spi.ExchangeRateProvider

파일의 내용은 SPI 구현의 정규화된 클래스 이름입니다.

com.baeldung.rate.impl.YahooFinanceExchangeRateProvider

4.3. 함께 넣어

마지막으로 exchange-rate-app 이라는 클라이언트 프로젝트 를 만들고 클래스 경로에 exchange-rate-api 의존성을 추가해 보겠습니다.

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

이 시점에서 애플리케이션에서 SPI를 호출할 수 있습니다 .

ExchangeRate.providers().forEach(provider -> ... );

4.4. 애플리케이션 실행

이제 모든 모듈을 빌드하는 데 집중해 보겠습니다.

mvn clean package

그런 다음 공급자를 고려하지 않고 Java 명령으로 애플리케이션을 실행합니다.

java -cp ./exchange-rate-api/target/exchange-rate-api-1.0.0-SNAPSHOT.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0-SNAPSHOT.jar com.baeldung.rate.app.MainApp

이제 java.ext.dirs 확장에 공급자를 포함하고 응용 프로그램을 다시 실행합니다.

java -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:./exchange-rate-impl/target:./exchange-rate-impl/target/depends -cp ./exchange-rate-api/target/exchange-rate-api-1.0.0-SNAPSHOT.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0-SNAPSHOT.jar com.baeldung.rate.app.MainApp

공급자가 로드된 것을 볼 수 있습니다.

5. 결론

잘 정의된 단계를 통해 Java SPI 메커니즘을 살펴보았으므로 이제 Java SPI를 사용하여 쉽게 확장하거나 교체할 수 있는 모듈을 만드는 방법을 확인해야 합니다.

우리의 예는 다른 기존 외부 API에 플러그인하는 힘을 보여주기 위해 야후 환율 서비스를 사용했지만, 프로덕션 시스템은 훌륭한 SPI 애플리케이션을 만들기 위해 제3자 API에 의존할 필요가 없습니다.

코드는 평소와 같이 Github 에서 찾을 수 있습니다 .

Generic footer banner