Загрузка экземпляра Singleton класса с помощью Java ServiceLoader

  1. У меня есть интерфейс в моем проекте.

  2. Конечные пользователи могут реализовать этот интерфейс и создать свои конкретные реализации.

  3. Всем пользователям рекомендуется следовать шаблону проектирования Singleton при создании реализаций.

  4. Пользователи будут хранить реализации в файле свойств.

  5. Я использую Java Reflection для загрузки классов, как показано ниже.

  6. Вышеупомянутый метод очень плохо влияет на производительность, и поэтому по разным причинам я решил перенести загрузочную часть в Java ServiceLoader.

  7. Но для загрузки классов ServiceLoader требуется общедоступный конструктор без аргументов, что является серьезным препятствием.

  8. Есть ли способ добиться загрузки класса с помощью Java ServiceLoader с частным конструктором, или я могу сделать одноэлементные реализации безопасными с помощью общедоступного конструктора без аргументов.

Я прочитал документацию и погуглил возможные решения, но не нашел ничего существенного.

Как сейчас загружаются классы:

Class klass = Class.forName(klassName);
Method method = klass.getDeclaredMethod("getInstance");
Object object = method.invoke(null);

Я просто хочу избавиться от этого файла свойств и перейти к Java ServiceLoader, как я перешел к другим классам, которые не были одиночными.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть как минимум несколько разных подходов, которые вы могли бы использовать.

Загружать только один экземпляр поставщика

Вероятно, лучшее решение — убедиться, что вы сами загружаете только один экземпляр провайдера. Это освобождает реализации провайдера от ответственности.

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, если он существует, в этом случае будет иметь приоритет над конструктором без аргументов.

Мне интересно узнать о потокобезопасности решений. Вы случайно не знаете, являются ли они потокобезопасными? Мне очень понравился последний вариант, но, к сожалению, я использую Java 8.

booyaakaashaa 16.06.2024 09:38

Будет ли второй и третий подход безопасно создавать и возвращать синглтон, полностью зависит от разработчиков, которые пишут реализации ServiceFactory и provider() соответственно. В первом подходе мой пример синхронизировал статический метод, поэтому загрузка поставщиков будет потокобезопасной (т. е. два потока, вызывающие getServices, не могут оба вызвать загрузку). Но для всех подходов вопрос о том, являются ли сами сервисные объекты потокобезопасными, зависит от того, как разработчики их реализуют.

Slaw 16.06.2024 14:31

Другие вопросы по теме