У меня есть интерфейс в моем проекте.
Конечные пользователи могут реализовать этот интерфейс и создать свои конкретные реализации.
Всем пользователям рекомендуется следовать шаблону проектирования Singleton при создании реализаций.
Пользователи будут хранить реализации в файле свойств.
Я использую Java Reflection для загрузки классов, как показано ниже.
Вышеупомянутый метод очень плохо влияет на производительность, и поэтому по разным причинам я решил перенести загрузочную часть в Java ServiceLoader.
Но для загрузки классов ServiceLoader требуется общедоступный конструктор без аргументов, что является серьезным препятствием.
Есть ли способ добиться загрузки класса с помощью Java ServiceLoader с частным конструктором, или я могу сделать одноэлементные реализации безопасными с помощью общедоступного конструктора без аргументов.
Я прочитал документацию и погуглил возможные решения, но не нашел ничего существенного.
Как сейчас загружаются классы:
Class klass = Class.forName(klassName);
Method method = klass.getDeclaredMethod("getInstance");
Object object = method.invoke(null);
Я просто хочу избавиться от этого файла свойств и перейти к Java ServiceLoader, как я перешел к другим классам, которые не были одиночными.




Есть как минимум несколько разных подходов, которые вы могли бы использовать.
Вероятно, лучшее решение — убедиться, что вы сами загружаете только один экземпляр провайдера. Это освобождает реализации провайдера от ответственности.
public final class ServiceRegistry {
private static List<Service> services;
public static synchronized List<Service> getServices() {
if (services == null) {
// The 'stream()' method and 'ServiceLoader.Provider` interface were
// added in Java 9. On older versions you can use the 'iterator()` method
// to do something similar.
services = ServiceLoader.load(Service.class).stream()
.map(ServiceLoader.Provider::get)
.toList(); // 'Stream::toList()' added in Java 16
}
return services;
}
private ServiceRegistry() {}
}
Затем вы можете передать эти экземпляры Service туда, где они вам нужны.
Другой вариант — использовать некоторую косвенность, сделав службу фабрикой.
public interface ServiceFactory {
Service getSingletonInstance();
}
Который вы бы загрузили как:
static List<Service> getServices() {
// The 'stream()' method and 'ServiceLoader.Provider` interface were
// added in Java 9. On older versions you can use the 'iterator()` method
// to do something similar.
return ServiceLoader.load(ServiceFactory.class).stream()
.map(ServiceLoader.Provider::get)
.map(ServiceFactory::getSingletonInstance)
.toList(); // 'Stream::toList()' added in Java 16
}
Можно создать любое количество экземпляров ServiceFactory. Это реализация фабрики, которая каждый раз возвращает один и тот же экземпляр Service.
Если в Java 9+ поставщики развернуты в неавтоматических модулях, то эти поставщики могут определить статический метод без аргументов с именем provider.
package com.example.provider;
import com.example.spi.Service;
public class Provider implements Service {
// can make creating the instance lazy if you want
private static final Provider INSTANCE = new Provider();
// return type must be assignable to Service
public static Provider provider() {
return INSTANCE;
}
private Provider() {} // constructor can now be private if desired
// ... service methods ...
}
Дескриптор модуля:
module provider {
provides com.example.spi.Service with
com.example.provider.Provider;
}
Обратите внимание, что метод provider, если он существует, в этом случае будет иметь приоритет над конструктором без аргументов.
Будет ли второй и третий подход безопасно создавать и возвращать синглтон, полностью зависит от разработчиков, которые пишут реализации ServiceFactory и provider() соответственно. В первом подходе мой пример синхронизировал статический метод, поэтому загрузка поставщиков будет потокобезопасной (т. е. два потока, вызывающие getServices, не могут оба вызвать загрузку). Но для всех подходов вопрос о том, являются ли сами сервисные объекты потокобезопасными, зависит от того, как разработчики их реализуют.
Мне интересно узнать о потокобезопасности решений. Вы случайно не знаете, являются ли они потокобезопасными? Мне очень понравился последний вариант, но, к сожалению, я использую Java 8.