자바 에서 JDK SPI 소스 코드 분석

19290 단어 자바
1. SPI 개요
SPI 는 모두 Service Provider Interface 라 고 하 는데 일종 의 서비스 발견 메커니즘 으로 제3자 에 의 해 실현 되 거나 확 장 된 API 에 사용 된다.
JDK SPI 사용:
  • META-INF/services 디 렉 터 리 에 '인터페이스 전체 제한 이름' 이라는 이름 의 파일 을 만 듭 니 다. 인터페이스 '실현 클래스 의 전체 제한 이름' eg: 파일 이름: com. union. jd. SpiService 파일 내용:
    com.union.jd.ASpiServiceImpl
    com.union.jd.BSpiServiceImpl
    com.union.jd.CSpiServiceImpl
    
  • 인터페이스 실현 클래스 classpath 에서
  • 메 인 프로그램 통과 java.util.ServiceLoader 도구 류 호출 실현 클래스
    ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);   
    //ServiceLoader实现了Iterable,所以可以直接使用增强for遍历
    for (DriverService driverService: serviceLoader){
    	System.out.println(driverService.getName());
    }
    
  • SPI 의 실현 류 는 반드시 무 참 구조 방법
  • 을 가 져 야 한다.
    2. ServiceLoader 소스 코드 분석
  • ServiceLoader util 패키지 에서 계승 할 수 없 는 final 클래스 이 고 Iterable 인터페이스 iterator() 방법 을 실현 하여 이 인터페이스의 모든 실현 대상 에 사용 합 니 다.
    public final class ServiceLoader<S>implements Iterable<S> {...}
    
  • ServiceLoader 클래스 에서 상수 PREFIX
    private static final String PREFIX = "META-INF/services/";
    
  • 를 정의 했다.
  • 정적 방법 load 실현
    //入参为接口的全限定名
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //获取当前线程上下文类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        //调用重载方法
        return ServiceLoader.load(service, cl);
    }
    //重载方法:参数1为接口的全限定名、参数二维当前线程上下文类加载器
    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
    {	//创建ServiceLoader对象
    	return new ServiceLoader<>(service, loader);
    }
    //ServiceLoader构造方法
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //校验Class对象是否存在,赋值给变量service
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //当"线程上下文类加载器"为null则获取"系统类加载器",赋值给变量loader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //security沙箱相关
        acc = (System.getSecurityManager() != null) ? AccessController.getContext():null;
        //调用reload方法
        reload();
    }
    //reload方法
    public void reload() {
        //清空缓存的实例对象
        providers.clear();
        //创建LazyIterator内部类对象,赋值给lookupIterator属性,iterator()遍历就是对该属性遍历
        lookupIterator = new LazyIterator(service, loader);
    }
    
    tip: 왜 load () 방법 에서 '현재 클래스 로 더' 나 '시스템 클래스 로 더' 가 아 닌 '스 레 드 컨 텍스트 클래스 로 더' 를 우선 사용 합 니까?[왜 Thread.currentThread().getContextClassLoader() 를 우선 사용 하고 직접 사용 하지 않 았 습 니까?시작 클래스 로 더 는 시스템 클래스 로 더 의 부모 로 더 입 니 다. 부모 로 더 가 하위 로 더 를 볼 수 없 기 때문에 클래스 를 불 러 올 수 없습니다. 따라서 '현재 클래스 로 더' 는 사용 할 수 없습니다.2. 많은 경우 에 로드 ServiceLoader.class.getClassLoader() 중의 jar 는 ClassLoader.getSystemClassLoader() 로 불 러 오 는 것 이 아니 라 ServiceLoader ServiceLoader.class.getClassLoader() 를 용기 로 하 는 환경 에서 classpath 로 불 러 오 는 것 이 아니 라 AppClassLoader java ee 에서 얻 은 것 은 tomcat 의 부모 로 더 입 니 다. 마찬가지 로 부모 로 더 가 서브 로 더 를 불 러 오 는 클래스 가 보이 지 않 아서 불 러 오지 않 습 니 다.따라서 우선 WebappClassLoader 을 사용 하고 '스 레 드 컨 텍스트 클래스 로 더' 가 null 일 때 만 사용 합 니 다 SystemClassLoader().
  • Lazy Iterator 내부 류 역시 Iterator 인 터 페 이 스 를 실현 했다. ServiceLoader 의 교체 기 방법 을 호출 할 때 사실은 Lazy Iterator 의 교체 기 방법
    private class LazyIterator implements Iterator<S> {...}
    
    //ServiceLoader 的迭代器方法,lookupIterator是LazyIterator的实例
    public Iterator<S> iterator() {
        return new Iterator<S>() {
    		...
            public boolean hasNext() {
                ...
                return lookupIterator.hasNext();
            }
    
            public S next() {
                ...
                return lookupIterator.next();
            }
    		...
        };
    }
    
  • 을 간접 적 으로 호출 하 는 것 이다.
  • Lazy Iterator 의 AppClassLoader, WebappClassLoader 방법 은 Lazy Iterator 류 의 hasNextService (), nextService () 방법
    public boolean hasNext() {
        ...
        return hasNextService();
        ...
    }
    
    public S next() {
    	...
    	return nextService();
    	...
    }
    
  • 을 각각 호출 했다.
  • Lazy Iterator 의 ContextClassLoader() 방법 으로 분석 하고 SystemClassLoader() 의 현재 인터페이스 파일 을 분석 하여 교체 기 현재 노드 의 실현 류 뒤에 실현 류 가 있 는 지 판단 합 니 다. 있 으 면 true 로 돌아 갑 니 다. 그렇지 않 으 면 false 로 돌아 갑 니 다. 실현 원 리 는 파일 읽 기 해석
    private boolean hasNextService() {
        ...
    }
    
  • 입 니 다.
  • Lazy Iterator 의 hasNext() 방법 분석
    private S nextService() {
        ...
        //通过上面传的类加载器,反射获取当前实现类的class对象
        c = Class.forName(cn, false, loader);
        ...
        //类型转换
        S p = service.cast(c.newInstance());
        //添加到缓存中
        providers.put(cn, p);
        //返回实例对象
        return p;
        ...
    }
    
  • 3. 총화
    소스 코드 분석 을 통 해 SPI 의 본질은 반 사 를 통 해 구체 적 인 실현 클래스 인 스 턴 스 를 얻 는 것 임 을 알 수 있 습 니 다. 사용자 가 next() 디 렉 터 리 에서 설명 을 해 야 불 러 올 수 있 고 프레임 워 크 개발 에서 이러한 방식 으로 사용자 정의 로 실현 할 수 있 습 니 다.hasNextService() 의 자동화 설정 은 Spring SPI 도구 인 SpringFactoriesLoader 를 통 해 이 루어 진 것 이다.
    Spring 의 실현 은 JDK 의 사상 과 같 습 니 다. 본질은 모두 반사 체 제 를 통 해 SPI 라 고 할 수 있 지만 실현 이 일치 하지 않 습 니 다.
  • JDK 가 사용 하 는 공구 류 는 META-INF/services, rt. jar 의 util 가방 에서
  • Spring 에서 사용 하 는 종 류 는 nextService() 이 며, org. spring from work. core. io. support 패키지 에서
  • 구별:
  • 파일 경로 에 따라 spring 설정 이 META - INF/spring. factories 에 있 습 니 다
  • 좋은 웹페이지 즐겨찾기