자바 의 SPI 이해

8900 단어 Javaspi
머리말
최근 면접 에서 SPI 에 대해 물 었 는데 대답 을 못 했 습 니 다.주로 자신의 이유 이기 도 합 니 다.자신 을 도랑 에 빠 뜨 렸 습 니 다.카 운 터 를 가 진 부모 위임 모델 에 대해 말 했 기 때문에 그 다음 에 부모님 위임 모델 을 파괴 하 는 장면 이 무엇 인지 물 었 습 니 다.그리고 저 는 SPI,JNDI,그리고 JDK 9 의 모듈 화가 부모님 위임 을 파괴 했다 고 말 했 습 니 다.
그리고 물 어보 면 자바 의 SPI 에 대한 이 해 를 말 해 보 세 요.그리고 나 서 나 는 어리둥절 해 졌 다.부모님 의 위임 을 망 칠 줄 만 알 았 고 어떻게 된 일 인지 알 았 을 뿐 깊이 이해 하지 못 했다.그러면 이번 에는 이 지식 을 잘 정리 하 겠 다.
SPI 가 뭐야?
SPI 는 모두 Service Provider Interface 라 고 부 릅 니 다.말 그대로 서 비 스 를 제공 하 는 인터페이스 라 는 뜻 입 니 다.상세 하 게 설명 하면 자바 가 제공 하 는 제3자 가 실현 하거나 확장 하 는 인터페이스 로 인터페이스의 동적 확장 을 실현 하여 제3자 의 실현 류 를 플러그 인 처럼 시스템 에 삽입 할 수 있 도록 하 는 것 입 니 다.
어...
이 해석 은 느낌 이 여전히 좀 어색 하 다.
그럼 그 본질 을 말 해 봐.
인터페이스의 구현 클래스 의 전체 제한 이름 을 파일 에 설정 합 니 다(파일 이름 은 인터페이스의 전체 제한 이름).서비스 로 더 에서 설정 파일 을 읽 고 실행 클래스 를 불 러 옵 니 다.실행 시 동적 인터페이스 교체 실현 클래스 를 실현 하 였 습 니 다.
SPI 예시
예 를 들 어 설명해 주세요.
우 리 는 프로젝트 를 만 든 후에 spi-interface 라 는 모듈 을 만 듭 니 다.

이 module 에서 우 리 는 인 터 페 이 스 를 정의 합 니 다.

/**
 * @author jimoer
 **/
public interface SpiInterfaceService {

  /**
   *     
   * @param parameter   
   */
  void printParameter(String parameter);
}
하나의 module 를 정의 합 니 다.이름 은 spi-service-one 이 고 pom.xml 에서 spi-interface 에 의존 합 니 다.
spi-service-one 에서 하나의 실현 류 를 정의 하여 SpiInterfaceService 인 터 페 이 스 를 실현 합 니 다.

package com.jimoer.spi.service.one;
import com.jimoer.spi.app.SpiInterfaceService;

/**
 * @author jimoer
 **/
public class SpiOneService implements SpiInterfaceService {
  /**
   *     
   *
   * @param parameter   
   */
  @Override
  public void printParameter(String parameter) {
    System.out.println("  SpiOneService:"+parameter);
  }
}
그리고 spi-service-one 의 resources 디 렉 터 리 에 디 렉 터 리 META-INF/services 를 만 듭 니 다.이 디 렉 터 리 에 SpiInterfaceService 인터페이스 라 는 전체 한정 이름 을 만 들 고 파일 내용 은 SpiOneService 라 는 실현 류 의 전체 한정 이름 을 기록 합 니 다.
효 과 는 다음 과 같 습 니 다:

모듈 을 하나 더 만 듭 니 다.이름 은 spi-service-one 이 고 spi-interface 에 의존 하 며,구현 클래스 SpiTwo Service 를 정의 하여 SpiInterfaceService 인 터 페 이 스 를 실현 합 니 다.

package com.jimoer.spi.service.two;
import com.jimoer.spi.app.SpiInterfaceService;
/**
 * @author jimoer
 **/
public class SpiTwoService implements SpiInterfaceService {
  /**
   *     
   *
   * @param parameter   
   */
  @Override
  public void printParameter(String parameter) {
    System.out.println("  SpiTwoService:"+parameter);
  }
}
디 렉 터 리 구 조 는 다음 과 같 습 니 다.

테스트 에 사용 할 module 을 만 듭 니 다.이름 은 spi-app 입 니 다.

pom.xml 에서 의존spi-service-onespi-service-two

<dependencies>
  <dependency>
    <groupId>com.jimoer.spi</groupId>
    <artifactId>spi-service-one</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>com.jimoer.spi</groupId>
    <artifactId>spi-service-two</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
</dependencies>
테스트 클래스 생 성

/**
 * @author jimoer
 **/
public class SpiService {

  public static void main(String[] args) {

    ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);
    Iterator<SpiInterfaceService> iterator = spiInterfaceServices.iterator();
    while (iterator.hasNext()){
      SpiInterfaceService sip = iterator.next();
      sip.printParameter("  ");
    }
  }
}
실행 결과:
저 는 SpiTwo Service:인자 입 니 다.
저 는 SpiOne Service:인자 입 니 다.
실행 결 과 를 통 해 SpiInterfaceService 인터페이스의 모든 실현 을 현재 프로젝트 에 불 러 오고 호출 을 실 행 했 음 을 알 수 있 습 니 다.

이 전체 코드 구 조 는 SPI 메커니즘 이 모듈 의 조립 을 프로그램 밖 에 두 었 음 을 알 수 있다.즉,인터페이스의 실현 은 프로그램 밖 에 있 을 수 있 고 사용 할 때 구체 적 인 실현 을 지정 해 야 한 다 는 것 이다.자신의 항목 에 동적 으로 불 러 옵 니 다.
SPI 메커니즘 의 주요 목적:
첫째,결합 을 풀기 위해 인터페이스 와 구체 적 인 실현 을 분리 하 는 것 이다.
둘째,프레임 의 확장 성 을 향상 시킨다.예전 에 프로그램 을 쓸 때 인터페이스 와 실현 이 모두 쓰 여 있 었 고 호출 자 는 사용 할 때 인터페이스 에 의존 하여 호출 을 했 으 며 구체 적 인 실현 류 를 선택 할 권리 가 없 었 다.
SPI 의 실현
그렇다면 SPI 가 어떻게 이 뤄 졌 는 지 살 펴 볼 까요?
위의 예 를 통 해 SPI 메커니즘 의 핵심 코드 는 다음 과 같다.

ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);
그럼ServiceLoader.load()방법의 소스 코드 를 살 펴 보 겠 습 니 다.

public static <S> ServiceLoader<S> load(Class<S> service) {
  ClassLoader cl = Thread.currentThread().getContextClassLoader();
  return ServiceLoader.load(service, cl);
}
Thread.currentThread().getContextClassLoader()보기;어떻게 된 일 인지 알 겠 습 니 다.이것 이 바로 스 레 드 컨 텍스트 클래스 로 더 입 니 다.스 레 드 컨 텍스트 클래스 로 더 는 부모 위임 모델 을 불 러 오 는 역순 서 를 만 들 기 위해 만 든 것 이기 때 문 입 니 다.
이 스 레 드 컨 텍스트 클래스 로 더 를 사용 하여 필요 한 SPI 서비스 코드 를 불 러 옵 니 다.이것 은 부모 클래스 로 더 를 요청 하여 클래스 로 더 를 완성 하 는 행위 입 니 다.이러한 행 위 는 실제 적 으로 통 합 된 것 입 니 다.부모 위임 모델 의 차원 구 조 는 클래스 로 더 를 역방향 으로 사용 하 는 것 으로 부모 위임 모델 의 일반적인 원칙 에 어 긋 나 지만 어 쩔 수 없 는 일 입 니 다.
자바 가상 머 신 깊이 이해(제3 판)
부모 의 위임 을 깨 뜨 린 것 임 을 알 았 지만 구체 적 으로 실현 하려 면 구체 적 으로 내 려 다 봐 야 한다.
ServiceLoader 에서 hasNext()를 구체 적 으로 실현 하 는 방법 을 찾 았 다 면 이 방법의 실현 을 계속 살 펴 보 자.

hasNext()방법 은 주로 hasNextService()방법 을 호출 했다.

//     
private static final String PREFIX = "META-INF/services/";

private boolean hasNextService() {
   if (nextName != null) {
     return true;
   }
   if (configs == null) {
     try {
     	//     +       
       String fullName = PREFIX + service.getName();
       //                ,       (           )
       if (loader == null)
         configs = ClassLoader.getSystemResources(fullName);
       else
         configs = loader.getResources(fullName);
     } catch (IOException x) {
       fail(service, "Error locating configuration files", x);
     }
   }
   while ((pending == null) || !pending.hasNext()) {
     if (!configs.hasMoreElements()) {
       return false;
     }
     pending = parse(service, configs.nextElement());
   }
   //   next()                    
   nextName = pending.next();
   return true;
 }
주로 META-INF/services/경로 의 인터페이스 전체 이름 을 제한 하 는 파일 을 불 러 온 다음 에 실현 클래스 의 클래스 경 로 를 찾 아 클래스 를 불 러 옵 니 다.
교체 기 가 어떻게 모든 실현 대상 을 꺼 내 는 지 계속 보 세 요.그럼 ServiceLoader 에서 교체 기 를 실현 한 next()방법 을 봐 야 겠 네요.

next()방법 은 주로 nextService()로 이 루어 집 니 다.그러면 nextService()방법 을 계속 보 세 요.

private S nextService() {
   if (!hasNextService())
     throw new NoSuchElementException();
   String cn = nextName;
   nextName = null;
   Class<?> c = null;
   try {
   //      ,     (    hasNext()      )。
     c = Class.forName(cn, false, loader);
   } catch (ClassNotFoundException x) {
     fail(service,
       "Provider " + cn + " not found");
   }
   if (!service.isAssignableFrom(c)) {
     fail(service,
       "Provider " + cn + " not a subtype");
   }
   try {
   	//             。
     S p = service.cast(c.newInstance());
     providers.put(cn, p);
     return p;
   } catch (Throwable x) {
     fail(service,
       "Provider " + cn + " could not be instantiated",
       x);
   }
   throw new Error();     // This cannot happen
 }
이곳 을 보면 어떻게 대상 을 만 들 었 는 지 알 수 있다.먼저 hasNext()에서 인터페이스의 실현 클래스 를 불 러 오고 인터페이스의 실현 클래스 가 존재 하 는 지 판단 한 다음 에 next()방법 에서 클래스 를 실례 화 합 니 다.
자바 에서 SPI 메커니즘 을 사용 하 는 기능 은 사실 매우 많다.예 를 들 어 JDBC,JNDI,그리고 Spring 에서 도 사용 되 고 심지어 RPC 프레임 워 크(Dubbo)에서 도 SPI 메커니즘 을 사용 하여 기능 을 실현 한다.
이상 은 자바 에서 SPI 에 대한 이해 의 상세 한 내용 입 니 다.자바 SPI 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!

좋은 웹페이지 즐겨찾기