Java ServiceLoader Nedir? SPI (Service Provider Interface) Nasıl Yazılır?
Java programlama dilinde yazılan servisler, farklı servis sağlayıcıları tarafından uygulanabilmektedir. Servisler genel olarak arayüzler veya soyut sınıflar ile yazılırken, servis sağlayıcıları ise bu arayüz servislerini uygulayan uygulayıcı sınıflardır.
Java programlama dilinin 6. versiyondan beri bulunan java.util.ServiceLoader
sınıfı ise, uygulamalarınızda yer alan farklı servis sağlayıcılarına erişerek, kullanmanıza imkan sağlamaktadır.
Örneğin bir servis yazarının RandomServiceProvider
adında bize bir arayüz sunduğunu varsayalım;
public interface RandomServiceProvider {
public Integer random(int start, int end);
}
Figure 1. random-service-spi projesi
|
Bu servisin görevi adından da anlaşılacağı üzere verilen iki tam sayı arasında rastegele bir sayı üretmesidir. Servis sağlayıcı sınıfları genel olarak arayüz veya soyut sınıf türünden olmaktadır.
Spesifikasyon belirleyicileri, farklı uygulayıcılar için yukarıdakine benzer arayüzleri API uygulayıcılarına açmaktadır. Java standartlarının geliştirim sürecine bakıldığında, buna benzer bir süreç sürdürülmektedir. API uygulayıcıları ise bu arayüzlere dönük kendi implementasyonlarını geliştirmektedir.
Fakat iş sadece arayüzü tanımlamak değildir, bunun yanında servis sağlayıcı kütüphanenin sisteminize standart bir yolla entegre edilebilmesi gerekmektedir. java.util.ServiceLoader
sınıfı bu noktada, servis uygulayıcılarına standart arayüz servislerine eklenti olarak eklenme imkanı sunmaktadır. Örneğin şimdi, RandomServiceProvider
arayüzünün kullanıcı talebine karşı farklı sağlayıcıları sunabiliyor olmasını sağlayalım. Bunun için bu servise iki statik metod daha ekleyeceğiz.
Java 8 ile birlikte arayüzlere static metodlar tanımlanabilmektedir. Java 8 öncesini kullanıyorsanız interface yerine abstract sınıfları da kullanabilirsiniz. |
public interface RandomServiceProvider {
public Integer random(int start, int end);
public static RandomServiceProvider getProvider(String providerName) {
return ...;
}
public static RandomServiceProvider getDefaultProvider() {
return ...;
}
}
RandomServiceProvider
sınıfına getProvider
ve getDefaultProvider
adlarında iki statik metod ekledik. Bunlardan getDefaultProvider()
çağrıldığında bu servisin varsayılan imlpementasyonunun kullanıcıya döndürülmesini istiyoruz. getProvider(..)
olanla ise metoda sunulan sağlayıcı ismiyle X
bir sağlayıcının döndürülmesini istiyoruz. İşte bu sağlayıcı sınıflar türünden uygulayıcı nesnelerin sistemde aranıp sunulması için ServiceLoader
sınıfından faydalanacağız. Fakat bu metodların içerisini ServiceLoader
ile yapılandırmadan evvel bir varsayılan sağlayıcı nesnesi oluşturma yoluna gidelim.
public class StandardRandomProvider implements RandomServiceProvider {
@Override
public Integer random(int start, int end) {
Random random = new Random();
int randomInt = random.nextInt((end - start + 1)) + start;
return randomInt;
}
}
Görüldüğü üzere RandomServiceProvider
sağlayıcı arayüzünü uygulayan bir StandardRandomProvider
sınıfı oluşturduk. Bu sınıf içerisindeki random()
metodu, Random
sınıfı üzerinden verilen aralıkta rastgele bir tamsayı üretmektedir.
ServiceLoader
sınıfının, bu sağlayıcıya erişip yükleyebilmesi için, sağlayıcının belirli bir formda Java projesine kayıt ettirilmesi gerekmektedir. Bunun için StandardRandomProvider
sınıfının yer aldığı projede META-INF/services
dizini altında RandomServiceProvider
sınıfının tam adıyla bir dosya oluşturulmalı ve bu dosyanın içine RandomServiceProvider
sınıfının tam adı yazılmalıdır.
com.kodcu.spi.RandomServiceProvider
dosyasını içi ise aşağıdaki gibidir.
com.kodcu.StandardRandomProvider
Bu şekilde artık StandardRandomProvider
servisini ServiceLoader sistemine göre yapılandırmış oluyoruz. Artık ServiceLoader
, sağlayıcı sınıfı arayıp bulabilir ve sisteme yükleyebilir.
Şimdi, RandomServiceProvider
arayüz içini uygulayıcılarını sunar şekilde yapılandırmaya devam edelim.
public interface RandomServiceProvider {
public Integer random(int start, int end);
public static RandomServiceProvider getDefaultProvider() {
return getProvider("com.kodcu.StandardRandomProvider"); (1)
}
public static RandomServiceProvider getProvider(String providerName) {
ServiceLoader<RandomServiceProvider> serviceLoader = ServiceLoader.load(RandomServiceProvider.class); (2)
for (RandomServiceProvider provider : serviceLoader) { (3)
String className = provider.getClass().getName(); (4)
if (providerName.equals(className)) (5)
return provider;
}
throw new RuntimeException(providerName + " provider is not found!"); (6)
}
}
1 | com.kodcu.StandardRandomProvider tam isimli varsayılan sağlayıcı sınıfını CLASSPATH ‘den yükler ve döndürür. |
2 | Yükleyeceği sağlayıcı arayüz türünden bir ServiceLoader nesnesi üretilir. |
3 | CLASSPATH içinde bulunan tüm RandomServiceProvider servis sınıfları türünden nesneler turlanır. |
4 | Bulunan RandomServiceProvider türünden sınıfın tam adı elde edilir. |
5 | Eğer bulunan sınıfın tam adı metoda düşen isimle aynı ise ilgili nesne metotdan döndürülür. |
6 | Eğer CLASSPATH içinde hiçbir servis uygulayıcısı bulunmadıysa bir istisna mesajı ile geliştirici bilgilendirilir. |
Şimdi varsayılan StandardRandomProvider
sağlayıcısına alternatif yeni bir sağlayıcı oluşturalım. İsmi ThreadedRandomProvider
olsun.
public class ThreadedRandomProvider implements RandomServiceProvider {
@Override
public Integer random(int start, int end) {
int randomInt = ThreadLocalRandom.current().nextInt(start,end);
return randomInt;
}
}
ThreadedRandomProvider
sınıfı rastgele sayı üretme işini Random
sınıfı yerine ThreadLocalRandom
sınıfı üzerinden yapıyor. Tabiki bu servis sınıfını yine kendi projesinde META-INF/services
altında yapılandırmalıyız.
com.kodcu.spi.RandomServiceProvider
dosyasını içi ise aşağıdaki gibidir.
com.kodcu.ThreadedRandomProvider
Böylece RandomServiceProvider
servisi StandardRandomProvider
gibi ThreadedRandomProvider
sınıfını da kullanıcılarına sunabilecektir.
Şimdi bu üç ayrı Java projeyesini random-service-spi
, standard-random-service
, threaded-random-service
çalıştıran random-service-app
adında bir proje oluşturalım. Bu proje random-service-spi
servisi üzerinden iki servis sağlayıcısını yükleyecek ve random()
metodlarını koşturacaktır.
public class App {
public static void main(String[] args) {
RandomServiceProvider defaultProvider = RandomServiceProvider.getDefaultProvider(); (1)
System.out.println(defaultProvider.random(1,1000)); (2)
RandomServiceProvider threadedProvider = RandomServiceProvider.getProvider("com.kodcu.ThreadedRandomProvider"); (3)
System.out.println(threadedProvider.random(1,1000)); (4)
}
}
1 | RandomServiceProvider.defaultProvider() metodu üzerinden varsayılan servis sağlayıcısını com.kodcu.StandardRandomProvider yükler. |
2 | StandardRandomProvider ‘ın random() metodu ile rastgele bir sayı üretip çıktılar. |
3 | RandomServiceProvider.getProvider() metodu üzerinden com.kodcu.ThreadedRandomProvider servis sağlayıcısını yükler. |
4 | ThreadedRandomProvider ‘ın random() metodu ile rastgele bir sayı üretip çıktılar. |
random-service-app
projesinin dosya sistemindeki görünümü aşağıdaki gibidir.
Böylece standart bir servise, ekler halinde ayrı uygulayıcılar tanıtmış olduk. ServiceLoader
API ise biz geliştiricilere standart bir yol sunmuş oluyor. Bu yaklaşımın birçok popüler Java Framework’lerinde de kullanıyor olduğunu söylemeliyim.
Aşağıda servis ve iki uygulayıcısını ve bunları tüketen App
sınıfını UML diagram halinde görebilirsiniz.
Kaynak kodlara buradaki bağlantıdan erişebilirsiniz.
Tekrar görüşmek dileğiyle
Tag:backend, service-loader, spi