JDK SPI 폭로

4906 단어
SPI의 전체 이름은 Service Provider Interface입니다. 모듈을 조립할 때 프로그램에서 동적 지시를 하지 않기 위해서는 서비스 발견 메커니즘이 필요합니다.JAVA SPI는 이러한 메커니즘을 제공하여 특정한 인터페이스를 위해 서비스를 실현하는 메커니즘이다.우리는 작은 예를 들어 설명한다. 우선 나는 인터페이스가 하나 있다. IHello.java
public interface IHello {
    void sayHello();
}

두 개의 인터페이스가 있는 구현 클래스HelloImpl1, HelloImpl2
public class HelloImpl1 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("  Impl1");
    }
}

public class HelloImpl2 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("  Impl2");
    }
}

MATE-INF 아래의 서비스 폴더에 인터페이스의 전체 한정명으로 명명된 파일이 있는데 그 안의 내용은 클래스의 전체 한정명을 실현하는 것이다.사용 시 도움말 클래스 ServiceLoader를 사용합니다. 구체적인 사용 방법은 다음과 같습니다.
    public static void main(String[] args){
        ServiceLoader s = ServiceLoader.load(IHello.class);
        Iterator iHelloIterator = s.iterator();
        while (iHelloIterator.hasNext()) {
            IHello iHello = iHelloIterator.next();
            iHello.sayHello();
        }
    }

우리는 대체적으로 IHello 유형의 실례를 검색한 후에 반복해서 실행했다. 우리는 구체적인 어떤 IHello의 실현 클래스가 조작되는지 밝히지 않았다. 그러면 누가 지정했는가?META-INF/services 아래에 있는 파일의 내용에 따라 결정됩니다.
com.test.demo.impl.HelloImpl2

그렇다면 Impl2에서 수행합니다.
com.test.demo.impl.HelloImpl1
com.test.demo.impl.HelloImpl2

둘 다 하는 거야.코드를 변경할 필요가 없는 상황에서 실행 클래스를 동적으로 전환할 수 있습니다.ServiceLoader는 어떻게 실행됩니까?먼저 이 유형의 ServiceLoader 를 만듭니다.
    public static  ServiceLoader load(Class service,ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

필요한 속성이 모두 준비되면reload를 시작합니다
    private ServiceLoader(Class 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();
    }

이 Lazy Iterator의 조작을 주목합니다. 검색 실현 클래스는 모두 이 클래스를 통해 진행됩니다.
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

이 방법은 Lazy Iterator가 다음 서비스가 있는지 판단하는 방법입니다. 이 fullName는 접두사(META-INF/services/)에 검색 서비스의name로 구성되어 있기 때문에 저희 파일의 경로와 이름은 이미 확정되었습니다. 이렇게 쓸 수 밖에 없습니다. 그렇지 않으면 검색할 수 없습니다. PARse 방법을 다시 한 번 보겠습니다. 바로 파일의 내용을 분석하여 Iterator에 넣는 것입니다.
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());
            }
            nextName = pending.next();
            return true;
        }

다음은 서비스를 얻는 과정입니다. 이전 단계를 거치면 기본적으로 실현 클래스의 이름을 해석할 수 있기 때문에Class를 통과해야 합니다.forName은 이 클래스의 Class 대상을 반사해서 불러오고 실례화 (newInstance) 하고 마지막으로 되돌려줍니다.
        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class> c = null;
            try {
                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
        }

이 ServiceLoader가 수행하는 프로세스가 바로 위에 있습니다.이 SPI의 대다수 개발자들은 익숙하지 않을 것이다. 왜냐하면 이것은 제조업체나 플러그인을 대상으로 하는 것이기 때문에 현재 많은 소프트웨어들이 이미 이런 것을 채택하거나 확장했다.예를 들면dubbo,jdbc 등은 알아볼 필요가 있다.

좋은 웹페이지 즐겨찾기