[Java] 서비스 로더(ServiceLoader)

아두치·2021년 3월 19일
1

서비스 로더(ServiceLoader)

🚀보통 자바 기초서에 등장하지 않기 때문에 서비스 로더가 생소하게 느껴진다.
서비스 로더는 주로 애플리케이션 내부에서 플러그인을 제공할 때 사용하는데, 전체적인 원리는 특정 기능을 제공하기 위한 인터페이스가 있고, 다양한 벤더 회사들이 이 인터페이스를 기반으로 자신만의 구체 서비스를 구현하게 된다.
사용자 입장에서는 어떤 벤더 회사가 어떻게 구현을 했든 공통 인터페이스만 가지고 있으면 각 벤더 회사 서비스의 장단점을 고려해 원하는 구현체만 골라서 사용하면 된다.
그리고 이러한 인터페이스를 SPI(Service Provider Interface)라고 한다.

API vs SPI

🚀API 는 사용자 입장에서 보면 라이브러리의 사용 방법이다.
반면에 SPI 는 서비스를 제작하는 사람의 입장에서 설계도라고 볼 수 있다.
그래서 보통 SPI 를 벤더사에 제공해서 구현체를 얻고, 이 구현체와 API 를 연결해서 API 를 사용자에게 제공하는 형태로 구성된다.

SPI 를 API 로도 사용할 순 있다.

서비스 로더 사용법

🚀 사용자가 불특정 플러그인을 클래스패스가 읽을 수 있는 장소에 다운로드했다고 가정하면, 애플리케이션은 해당 클래스패스를 이용해 SPI 를 구현(implements) 하고 있는 모든 구현체(인스턴스)를 애플리케이션 내부로 가져와야한다.
그리고 원하는 조건에 맞는 인스턴스를 골라서 사용하면 된다.

이 과정을 구현하기 위해 개발자가 리플렉션을 사용해 직접 구현할 순 있지만 친절하게도 자바에서 라이브러리 형태로 서비스 로더를 제공한다.

java.util.ServiceLoader

우선 간단한 테스트를 위해 SPI 를 먼저 작성해보자.

public interface RandomInt{
	public int ranmdom();
}

그리고 벤더 회사라고 가정하고 이 SPI 를 구현하는 구현체를 작성해보자.

package com.adduci;

public class MyRandomInt implements RandomInt{
	@Override
        public int random(){
        	return new Random().nextInt(10);
        }
}

이 벤더 회사는 0~9까지의 랜덤 정수 하나를 반환하는 기능을 제공해준다.

이제 해야할 일은 이 클래스 파일을 클래스 로더가 찾을 수 있는 공간으로 이동시키는 일이다.
어디든 상관은 없다.
클래스 패스에 포함되는 디렉터리이기만 하면 된다.

그리고 META-INF/services 디렉터리에 텍스트 파일을 하나 만들어서 다음과 같이 입력하고 RandomInt 라는 이름으로 저장한다. SPI 에 패키지가 있다면 패키지 네임까지 입력하고 저장해야한다.

com.adduci.MyRandomInt

UTF-8 로 인코딩되어야 된다.

이제 애플리케이션 내에서 불러오기만 하면 된다.

ServiceLoader<RandomInt> loader = ServiceLoader.load(RandomInt.class);

이렇게 작성하면 서비스 로더가 META-INF/service 디렉터리에서 SPI 이름으로 된 파일에 적힌 클래스들을 로드한다.

로드 한 서비스들을 사용하는 방법은 iterator 메소드를 호출해서 모든 서비스에 접근할 수 있다.

Iterator<RandomInt> iterator = loader.iteratr();

for(RandomInt service : iterator){
	System.out.println(service.random());
}

물론 여기서 각 서비스의 내부를 조사해서 원하는 서비스일 경우에만 선택해서 사용해도 된다. 그거라고 제공하는 라이브러리이기도 하다.

또한 stream 메소드를 호출하면 ServiceLoader.Provider 클래스의 인스턴스로 구성된 스트림을 반환해준다.
그리고 이 스트림의 요소인 Provider 클래스는 해당 서비스의 인스턴스를 반환해주는 get 메소드와 타입을 반환해주는 type 메소드를 제공한다.
get 메소드는 호출될 때 인스턴스를 생성한다.

loader.stream().map(Service.Provider::get).findFirst();

위 코드는 서비스 구현체 중 첫번째로 찾는 구현체를 가져오는 코드다.

와닿지가 않는다..

🚀 첫번째 이유는 내가 설명을 너무 못해서 그렇고 두번째 이유는 서비스 로더를 사용할만한 상황이 흔치 않기 때문일 것 같다.
그나마 가장 친숙한 상황은 우리가 만든 IDE 에서 사용자들이 프로젝트를 생성하고 개발하는데 프로젝트에서 사용할 플러그인을 추가 및 편집하는 화면이지 않을까 생각된다.
그 화면에서는 모든 플러그인 목록이 다 출력되어야하고, 사용자가 선택한 플러그인만 애플리케이션 내부에서 인스턴스화 해야한다.

외국 포럼에서 본적이 있는데 ServiceLoader 를 권장하지 않는 개발자도 꽤 있었던 것 같다.
정확한 이유는 기억이 안나는데 ServiceLoader 대신 ResourceFinder 를 추천했다.
다만 ResourceFinder 의 경우 자바 기본 라이브러리는 아니고 라이센스가 달린 라이브러리이기 때문에 잘 사용해야할 것 같다.

profile
HAVE YOU TRIED IT?

5개의 댓글

comment-user-thumbnail
2021년 3월 29일

감사합니다~!

답글 달기
comment-user-thumbnail
2021년 6월 9일

잘 봤습니다. 감사합니다~

답글 달기
comment-user-thumbnail
2022년 1월 9일

감사합니다

답글 달기
comment-user-thumbnail
2022년 2월 18일

그거라고 제공하는 라이브러리이기도 하다
-> 그러라고 제공하는 라이브러리이기도 하다

답글 달기
comment-user-thumbnail
2022년 2월 18일

이 글과
https://stackoverflow.com/a/1200108
이 링크로 대강 감을 잡았네요. 감사합니다.

답글 달기