자바 실천 SPI 메커니즘 및 원본 분석

14685 단어 자바SPI
1.개념
본 격 적 으로 오늘 의 핵심 내용 에 들 어가 기 전에 계원 은 SPI 체제 에 관 한 개념 을 소개 하고 마지막 으로 실천 소스 코드 를 제공 할 것 이다.
SPI 즉 Service Provider Interface 는 JDK 에 내 장 된 동적 서비스 제공 발견 메커니즘 으로 실행 시 동적 로드 인터페이스의 실현 클래스 로 이해 할 수 있다.심지어 SPI 메커니즘 과 디자인 모델 중의 전략 모델 을 연결 할 수 있다.
SPI 메커니즘:

위의 그림 에서 SPI 메커니즘 이해:표준화 인터페이스+정책 모드+프로필;
SPI 메커니즘 의 핵심 사상:시스템 설계 의 각 추상 은 종종 서로 다른 실현 방안 이 있다.대상 을 대상 으로 하 는 디자인 에서 일반적으로 추천 모듈 간 에 인터페이스 프로 그래 밍 을 바탕 으로 하고 모듈 간 에 실현 클래스 에 대해 하 드 코딩 을 하지 않 는 다.코드 에 구체 적 인 실현 류 가 포함 되면 삽입 가능 한 원칙 을 위반 하고 하나의 실현 이 필요 하 다 면 코드 를 수정 해 야 한다.모듈 을 조립 할 때 프로그램 에서 동적 으로 가리 키 지 않 을 수 있 도록 서비스 발견 메커니즘 이 필요 하 다
사용 필드:
  • 1.데이터베이스 구동 로드:서로 다른 업 체 의 데이터 베 이 스 를 직면 하여 JDBC 는 서로 다른 유형의 데이터 베 이 스 를 불 러 와 야 합 니 다.
  • 2.로그 인터페이스 구현:SLF4J 에서 서로 다른 로그 구현 클래스 를 불 러 옵 니 다.
  • 3.계원 은 실제 개발 에서 도 SPI 체 제 를 사용 했다.서로 다른 기기 플랫폼 의 결과 파일 업로드 에 대해 구체 적 인 결 과 를 분석 해 야 한다.파일 이 다 르 고 해석 논리 가 다 르 기 때문에 SPI 체 제 를 사용 하면 결합 을 해제 하고 유지 원 가 를 낮 출 수 있다.
  • SPI 메커니즘 사용 약정:
    위의 그림 에서 우 리 는 SPI 의 세 부분 을 뚜렷하게 알 수 있다.인터페이스+구현 클래스+프로필;따라서 프로젝트 에서 SPI 메커니즘 을 이용 하려 면 다음 과 같은 약정 을 따라 야 한다.
  • 서비스 제공 자가 인터페이스의 구체 적 인 실현 을 제공 한 후에 jar 패키지 의 META-INF/services 디 렉 터 리 에서'인터페이스 전체 제한 이름'이라는 이름 을 가 진 파일 을 만 듭 니 다.내용 은 실현 류 의 전체 제한 이름 입 니 다.
  • 메 인 프로그램 은 java.util.Service Loder 동적 마 운 트 실현 모듈 을 통 해 META-INF/services 디 렉 터 리 에 있 는 프로필 을 검색 하여 구현 클래스 의 전체 제한 이름 을 찾 아 JVM 에 클래스 를 불 러 옵 니 다.
  • 주의:SPI 를 제외 하고 저 는 최신 자바 구조 프로젝트 실전 튜 토리 얼+대형 공장 면접 문제 창고 도 발 표 했 습 니 다.여 기 를 클릭 하 십시오무료 로 얻 을 수 있 습 니 다.소 백 은 들 어가 지 마 세 요!
    2.실천
    전체 패키지 구 조 는 그림 과 같다.

    새 표준화 인터페이스:
    
    public interface SayService {
      void say(String word);
    }
    두 개의 실현 류 를 세우다
    
    @Service
    public class ASayServiceImpl implements SayService {
      @Override
      public void say(String word) {
        System.out.println(word + " A say: I am a boy");
      }
    }
    
    
    @Service
    public class BSayServiceImpl implements SayService {
      @Override
      public void say(String word) {
        System.out.println(word + " B say: I am a girl");
      }
    }
    새 META-INF/services 디 렉 터 리 와 프로필(인터페이스 전체 이름 으로 지정)
    설정 파일 내용 은 클래스 전체 제한 이름 입 니 다.
    
    com.qxy.spi.impl.ASayServiceImpl
    com.qxy.spi.impl.BSayServiceImpl
    단일 측량
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class SpiTest {
    
      static ServiceLoader<SayService> services = ServiceLoader.load(SayService.class);
    
      @Test
      public void test1() {
        for (SayService sayService : services) {
          sayService.say("Hello");
        }
      }
    
    }
    결실
    Hello A say: I am a boy
    Hello B say: I am a girl
    3.소스 코드
    원본 코드 의 주요 로드 프로 세 스 는 다음 과 같 습 니 다.
    응용 프로그램 이 ServiceLoader.load 방법 을 호출 합 니 다.ServiceLoader.load 방법 에서 먼저 새로운 ServiceLoader 를 만 들 고 이 클래스 의 구성원 변 수 를 예화 합 니 다.
  • loader(ClassLoader 형식,클래스 로 더)
  • acc(AccessControl Context 형식,액세스 컨트롤 러)
  • providers(LinkedHashMap형식,캐 시 불 러 오 는 데 성공 한 클래스)
  • lookupIterator(교체 기 기능 실현)
  • 응용 프로그램 은 교체 기 인 터 페 이 스 를 통 해 대상 인 스 턴 스 를 가 져 옵 니 다.ServiceLoader 는 구성원 변수 providers 대상(LinkedHashMap형식)에 캐 시 인 스 턴 스 대상 이 있 는 지 먼저 판단 하고 캐 시 가 있 으 면 바로 되 돌려 줍 니 다.캐 시가 없 으 면 클래스 의 불 러 오기 가 실 행 됩 니 다.
  • META-INF/services/의 프로필 을 읽 고 실례 화 될 수 있 는 모든 종류의 이름 을 얻 을 수 있 습 니 다.주의해 야 할 것 은 ServiceLoader 는 jar 가방 을 넘 어 META-INF 의 프로필 을 가 져 올 수 있 습 니 다.
  • 반사 방법 Class.forName()을 통 해 클래스 대상 을 불 러 오고 인 스 턴 스()방법 으로 클래스 를 예화 합 니 다.
  • 실례 화 된 클래스 를 providers 대상 에 캐 시 하고(LinkedHashMap형식)실례 대상 으로 되 돌려 줍 니 다.
  • 
    public final class ServiceLoader<S>
      implements Iterable<S>
    {
      //             
      private static final String PREFIX = "META-INF/services/";
    
      //        
      // The class or interface representing the service being loaded
      private final Class<S> service;
    
      //          
      // The class loader used to locate, load, and instantiate providers
      private final ClassLoader loader;
    
      //   ServiceLoader           
      // The access control context taken when the ServiceLoader is created
      private final AccessControlContext acc;
    
      //               ,  key         
      // Cached providers, in instantiation order
      private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
      //             
      // The current lazy-lookup iterator
      private LazyIterator lookupIterator;
    
      
      public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
      }
    
      private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
      }
    
      private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
      {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                          cause);
      }
    
      private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
      {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
      }
    
      private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
      {
        fail(service, u + ":" + line + ": " + msg);
      }
    
      // Parse a single line from the given configuration file, adding the name
      // on the line to the names list.
      //               
      private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                 List<String> names)
        throws IOException, ServiceConfigurationError
      {
        String ln = r.readLine();
        if (ln == null) {
        	//-1      
          return -1;
        }
        //     '#'  ,     '#'        ,'#'           
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
        	//      :' '、'\t'
          if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
            fail(service, u, lc, "Illegal configuration-file syntax");
          int cp = ln.codePointAt(0);
          //      char         Java      
          if (!Character.isJavaIdentifierStart(cp))
            fail(service, u, lc, "Illegal provider-class name: " + ln);
          	//                Java   
          for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
            cp = ln.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
              fail(service, u, lc, "Illegal provider-class name: " + ln);
          }
          //      
          if (!providers.containsKey(ln) && !names.contains(ln))
            names.add(ln);
        }
        return lc + 1;
      }
    
      
      private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
      {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
          in = u.openStream();
          r = new BufferedReader(new InputStreamReader(in, "utf-8"));
          int lc = 1;
          while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
          fail(service, "Error reading configuration file", x);
        } finally {
          try {
            if (r != null) r.close();
            if (in != null) in.close();
          } catch (IOException y) {
            fail(service, "Error closing configuration file", y);
          }
        }
        return names.iterator();
      }
    
      // Private inner class implementing fully-lazy provider lookup
      //
      private class LazyIterator
        implements Iterator<S>
      {
    
        Class<S> service;
        ClassLoader loader;
        //      URL  
    	  Enumeration<URL> configs = null; 
    	  //                 
    	  Iterator<String> pending = null;
    	  //                  
    	  String nextName = null;
    
        private LazyIterator(Class<S> service, ClassLoader loader) {
          this.service = service;
          this.loader = loader;
        }
    
        private boolean hasNextService() {
          if (nextName != null) {
            return true;
          }
          if (configs == null) {
            try {
            //     ,META-INF/services +     
              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());
          }
          //               
          nextName = pending.next();
          return true;
        }
    
        private S nextService() {
          if (!hasNextService())
            throw new NoSuchElementException();
          String cn = nextName;
          nextName = null;
          Class<?> c = null;
          try {
          //    Class  
            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());
             //     ,    ,Key:        ,Value:     
            providers.put(cn, p);
            return p;
          } catch (Throwable x) {
            fail(service,
               "Provider " + cn + " could not be instantiated",
               x);
          }
          throw new Error();     // This cannot happen
        }
    
        public boolean hasNext() {
          if (acc == null) {
            return hasNextService();
          } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
              public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
          }
        }
    
        public S next() {
          if (acc == null) {
            return nextService();
          } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
              public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
          }
        }
    
        public void remove() {
          throw new UnsupportedOperationException();
        }
    
      }
    
      
      public Iterator<S> iterator() {
        return new Iterator<S>() {
    
          Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
    
          public boolean hasNext() {
            if (knownProviders.hasNext())
              return true;
            return lookupIterator.hasNext();
          }
    
          public S next() {
            if (knownProviders.hasNext())
              return knownProviders.next().getValue();
            return lookupIterator.next();
          }
    
          public void remove() {
            throw new UnsupportedOperationException();
          }
    
        };
      }
    
      
      public static <S> ServiceLoader<S> load(Class<S> service,
                          ClassLoader loader)
      {
      //   ServiceLoader   
        return new ServiceLoader<>(service, loader);
      }
    
      
      public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
      
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
          prev = cl;
          cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
      }
    
      
      public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
      }
    
    }
    4.총화
    SPI 메커니즘 이 실제 개발 에 사용 되 는 장면 도 많다.특히 통 일 된 기준의 서로 다른 제조 업 체 가 실현 하고 계원 도 바로 SPI 메커니즘(그러나 약간 개선 하여 자원 의 낭 비 를 너무 많이 불 러 오지 않도록 한다)을 이용 하여 서로 다른 기술 플랫폼 의 결과 파일 분석 수 요 를 실현 한다.
    장점.
    자바 SPI 메커니즘 을 사용 하 는 장점 은 결합 을 실현 하고 제3자 서비스 모듈 의 조립 제어 의 논리 와 호출 자의 업무 코드 를 분리 하 는 것 이지 결합 하 는 것 이 아니다.응용 프로그램 은 실제 업무 상황 에 따라 프레임 워 크 확장 을 사용 하거나 프레임 워 크 구성 요 소 를 교체 할 수 있 습 니 다.
    결점.
    ServiceLoader 도 사용 하 는 지연 로 딩 이 라 고 할 수 있 지만 기본적으로 모든 것 을 옮 겨 다 니 며 얻 을 수 있 습 니 다.즉,인터페이스의 실현 류 는 모두 로 딩 하고 예화 할 수 있 습 니 다.만약 당신 이 어떤 실현 류 를 사용 하고 싶 지 않다 면,그것 도 로드 되 고 예화 되 어 낭 비 를 초래 할 것 입 니 다.
    소스 코드 전송 문:SPI Service
    자바 실천 SPI 체제 및 소스 코드 분석 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 자바 SPI 체제 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기