Notice
Recent Posts
Recent Comments
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

Grobble

Java9 Service Loader 본문

카테고리 없음

Java9 Service Loader

smilu97 2022. 6. 11. 17:03

Service, ServiceLoader가 무엇인지

Service는 Java9 부터 지원되는 기능이며, 어떠한 기능에 대해 정의하는 인터페이스 혹은 추상 클래스를 말한다. 어떤 Service에 대하여, 각 jar 파일은 자신의 META-INF/services 폴더 안에 자기 자신이 제공하는 ServiceProvider를 명시할 수 있고, ServiceLoader는 이 META-INF/services 폴더 안을 참조해서 특정 Service에 대한 ServiceProvider 들을 탐색한다.

ServiceProvider는 Service에 대한 구현체여야 하고 public concrete여야 한다. 그리고 ServiceLoader가 해당 ServiceProvider를 Reflection을 이용해서 생성하기 때문에 constructor에서 인자를 받지 않아야 한다.

예시: 상황 가정

예를 들어, 어떤 jar 파일 a.jar, b.jar, c.jar 가 있다고 하자.

a.jar

a.jar 안에는 DBClient 가 정의되어 있다고 하자.

// a.jar$org/sample/a/DBClient.java
package org.sample.a;
​
public interface DBClient {
  void connect(String host, int port);
  void save(long key, byte[] value);
  byte[] load(long key);
  void close();
}

b.jar

그리고 b.jar 안에는

// b.jar$org/sample/b/MemoryDBClient.java
package org.sample.b;
​
public class MemoryDBClient implements DBClient {
  ...
}

라는 MemoryDBClient 가 정의되어 있고 b.jar 안의 META-INF/services/org.sample.a.DBClient 파일안에

// b.jar$META-INF/services/org.sample.a.DBClient
org.sample.b.MemoryDBClient

라는 내용이 들어있다고 하자.

c.jar

그리고 c.jar 안에는

// c.jar$org/sample/c/RedisDBClient.java
package org.sample.c;
​
public class RedisDBClient implements DBClient {
  ...
}

라는 RedisDBClient 가 정의되어 있고 c.jar 안의 META-INF/services/org.sample.a.DBClient 파일 안에

// c.jar$META-INF/services/org.sample.a.DBClient
org.sample.c.RedisDBClient

라는 내용이 들어있다고 하자.

예시: ServiceLoader

이 상태에서 어떤 ClassLoader가 a.jar, b.jar, c.jar 를 참조하고 있는 상황이라면 아래와 같은 동작이 나타나야 한다.

/**
 * Output:
 * org.sample.b.MemoryDBClient
 * org.sample.c.RedisDBClient
 */
@Test
public void testServiceLoader() {
  ClassLoader cl = new URLClassLoader(new URL[] {
    new URL("./a.jar"),
    new URL("./b.jar"),
    new URL("./c.jar")
  });
  
  Class<?> targetService = cl.loadClass("org.sample.a.DBClient");
  for (Object service: ServiceLoader.load(targetService, cl)) {
    System.out.println(service.getClass().getName());
  }
}

ServiceLoader 구현

ServiceLoader는 Iterable 구현체인데, ServiceLoader의 Iterator는 두 종류의 하위 Iterator를 차례대로 순회한다. ModuleServicesLookupIterator는 named module에 소속되어 있는 ServiceProvider들을 순회하며, LazyClassPathLookupIterator는 소속된 모듈이 없거나 소속된 모듈에 이름이 없는 ServiceProvider들을 순회한다.

ModuleServicesLookupIterator

ModuleServiceLookupIterator는 ServiceCatalog라는 객체들에서 ServiceProvider들을 받아 순회한다. 그리고 ServiceCatalog는 ClassLoader마다, ModuleLayer마다 독립적으로 하나씩 가지고 있게 된다. 그래서 인자로 받은 ClassLoader와 관련된 ModuleLayer들도 찾아야 하는데, 이 매핑 정보(CLV)도 ModuleLayer가 생성될 때 미리 만들어진다.

하나의 ClassLoader에서 ServiceProvider들을 찾기 까지의 과정은 대략 위 그림과 같다. 그림에는 CLV가 2개 있는데, 이름만 같고 다른 객체이다. ClassLoader에서 ModuleLayer들을 매핑해주는 CLV의 정보는 각 ModuleLayer가 만들어질 때 미리 등록된다.

어떤 모듈은 반드시 어떤 ClassLoader에 속하는데, ModuleLayer에는 속하지 않을 수도 있다. 그래서 어떤 모듈에서 ServiceProvider가 발견되었을 때, 이것을 등록하는 과정에서 해당 모듈이 속하는 ModuleLayer가 존재한다면 그 ModuleLayer의 ServiceCatalog에 등록이 되고, 그렇지 않다면 ClassLoader의 ServiceCatalog에 등록이 된다. 즉, ModuleLayer를 통해 접근되는 ServiceCatalog가 더 선호된다.

LazyClassPathLookupIterator

ServiceLoader가 수행될 때 ClassLoader.getResources 를 이용해서 META-INF/services/org.sample.a.DBClient 파일(예시를 기준으로)을 읽어들인다.

이 과정에서 발견된 ServiceProvider들은 해당 클래스가 속한 모듈이 named 모듈일 경우 무시한다. 핀포인트 내부에서의 경우 모두 pinpoint.module 모듈 안에 존재하기 때문에 무조건 named 모듈에 속하게 되어 모두 무시된다.