자바 실천 SPI 메커니즘 및 원본 분석
본 격 적 으로 오늘 의 핵심 내용 에 들 어가 기 전에 계원 은 SPI 체제 에 관 한 개념 을 소개 하고 마지막 으로 실천 소스 코드 를 제공 할 것 이다.
SPI 즉 Service Provider Interface 는 JDK 에 내 장 된 동적 서비스 제공 발견 메커니즘 으로 실행 시 동적 로드 인터페이스의 실현 클래스 로 이해 할 수 있다.심지어 SPI 메커니즘 과 디자인 모델 중의 전략 모델 을 연결 할 수 있다.
SPI 메커니즘:
위의 그림 에서 SPI 메커니즘 이해:표준화 인터페이스+정책 모드+프로필;
SPI 메커니즘 의 핵심 사상:시스템 설계 의 각 추상 은 종종 서로 다른 실현 방안 이 있다.대상 을 대상 으로 하 는 디자인 에서 일반적으로 추천 모듈 간 에 인터페이스 프로 그래 밍 을 바탕 으로 하고 모듈 간 에 실현 클래스 에 대해 하 드 코딩 을 하지 않 는 다.코드 에 구체 적 인 실현 류 가 포함 되면 삽입 가능 한 원칙 을 위반 하고 하나의 실현 이 필요 하 다 면 코드 를 수정 해 야 한다.모듈 을 조립 할 때 프로그램 에서 동적 으로 가리 키 지 않 을 수 있 도록 서비스 발견 메커니즘 이 필요 하 다
사용 필드:
위의 그림 에서 우 리 는 SPI 의 세 부분 을 뚜렷하게 알 수 있다.인터페이스+구현 클래스+프로필;따라서 프로젝트 에서 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 를 만 들 고 이 클래스 의 구성원 변 수 를 예화 합 니 다.
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 체제 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.