CDI производит по сравнению с шаблоном локатора сервисов

Пожалуйста, прочитайте мой сценарий ниже и скажите мне, какой подход лучше в моем случае:

  1. CDI с методом производителя
  2. шаблон поиска сервисов

Сценарий:

  • Есть интерфейс, например ConfigurationManager.
  • Есть три совершенно разных его реализации. Каждый из них находится в отдельном EAR, ограниченном необходимыми библиотеками. У нас есть MemoryConfigurationManager, FileConfigurationManager и JpaConfigurationManager.
  • Есть приложение (EAR). Необходимо использовать одно внедрение ConfigurationManager.

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

Здесь я не могу использовать простой CDI, потому что реализации находятся в разных EAR, и приложение также находится в отдельном ухе, поэтому у них разные загрузчики классов. Простой @Inject не работает между EAR.

Итак, у меня есть два пути:

  • Если я хочу использовать CDI, мне нужно использовать метод @Produces. Он предоставляет способ внедрения объектов, которые не являются bean-компонентами.
  • Я могу использовать шаблон Service Locator. Этот вид поиска работает быстро, потому что у него есть собственный кеш.

Конечно, за методом CDI @Produces я также могу использовать шаблон Service Locator. URL-адреса службы для различных реализаций берутся из внешнего файла конфигурации. Если кто-то создает новую реализацию ConfigurationManager и устанавливает ее в контейнер Java EE, ему необходимо поместить новый URL-адрес службы в файл конфигурации.

Так какой подход лучше? (1) или (2)? Или есть другой понятный способ реализовать этот функционал? :-)

Я предпочитаю (2).
Я думаю, что производители CDI + приносят больше ненужных классов.


person zappee    schedule 25.11.2014    source источник


Ответы (2)


Я бы меньше думал о том, как получить Service-Proxy, в этом случае оба ваших сценария будут работать одинаково (как вы уже выяснили, вы даже можете аннотировать свой ServiceLocator с помощью @Produces).

Подумайте о том, как класс, использующий Сервис, получит экземпляр прокси. Вы хотите прочитать свойства и найти службу в каждом конструкторе? Будет ли у вас фабрика с одним методом для каждой реализации? Загрузка SPI-сервиса? Как вы собираетесь тестировать классы с помощью сервиса?

Во всех этих случаях внедрение зависимостей имеет большое преимущество, поскольку оно разделяет проблемы и определяет четкие зависимости между клиентом и реализацией. Моя лучшая практика: всякий раз, когда у вас есть выбор, выбирайте (C)DI. Если вы объедините подход ServiceLocator/PropertyReader с аннотацией @Produces, у вас даже не будет больше классов для написания, но вы получите много взамен.

person Jan Galinski    schedule 26.11.2014
comment
Итак, вы предлагаете использовать cdi. Думал над вашим комментарием и согласен с вами. Я собираюсь реализовать это с помощью cdi, и я вернусь к вам. Спасибо. - person zappee; 27.11.2014

Итак, я создал несколько классов для CDI. Он выглядит красиво и отлично работает.

класс квалификатора с типами:

@Qualifier
@Retention(RUNTIME)
@Target( {TYPE, METHOD, FIELD, PARAMETER} )
public @interface DaoQualifier
{
    public enum DatasourceType { FILE, JPA, MEMORY }

    public DatasourceType type();
}

factory, он возвращает правильный экземпляр интерфейса:

@ApplicationScoped
public class ConfigurationReaderDaoFactory implements Serializable
{
    private static final long serialVersionUID = -7097322271912690164L;
    private static final Logger LOGGER = Logger.getLogger( ConfigurationReaderDaoFactory.class.getName() );
    private final String LOG_MESSAGE = "getting ConfigurationReaderDao ejb remote reference for %s datasource";

    @Produces
    @DaoQualifier(type = DatasourceType.MEMORY)
    public ConfigurationReaderDao getMemoryDao() throws NamingException
    {
        LOGGER.finer( String.format(LOG_MESSAGE,  DatasourceType.MEMORY) );

        String jndi = ""java:global/earName/ejbName/MemoryConfigurationReaderDao!a.b.c.ConfigurationReaderDao";";
        return (ConfigurationReaderDao) InitialContextGenerator.getInitialContext().lookup(jndi);
    }

    @Produces
    @DaoQualifier(type = DatasourceType.JPA)
    public ConfigurationReaderDao getJpaDao() throws NamingException
    {
        LOGGER.finer( String.format(LOG_MESSAGE,  DatasourceType.JPA) );

        String jndi = ""java:global/earName/ejbName/JpaConfigurationReaderDao!a.b.c.ConfigurationReaderDao";";
        return (ConfigurationReaderDao) InitialContextGenerator.getInitialContext().lookup(jndi);
    }
    ...
}

легко использовать его с аннотацией @Inject:

public class ConfigurationReaderService
{
    @Inject
    @DaoQualifier(type = DatasourceType.MEMORY)
    private ConfigurationReaderDao configurationReaderDaoService;

    public String getStringSystemParam(final String key)
    {
        String value = null;

        if (key != null)
        {
            value = configurationReaderDaoService.getStringSystemParam(key);
        }
    }
}

Мне нравится это решение, потому что его легко читать и понимать.

У меня осталось только три вопроса:

(1) Последняя небольшая вещь, которую мне нужно выяснить, это: в моей текущей реализации параметр типа жестко закодирован:

@Inject
@DaoQualifier(type = DatasourceType.MEMORY)
private ConfigurationReaderDao configurationReaderDaoService;

Параметр типа исходит из конфигурации приложения, которой управляет администратор. Так что мне как-то нужно динамически ввести правильную реализацию интерфейса, и это зависит от простого значения строковой переменной.

Я думаю, мне нужно как-то использовать аннотацию @Any.

(2) Я пытался использовать аннотацию @RequestScoped в начале класса ConfigurationReaderDaoFactory, но это не сработало. Я получил WELD-001303: нет активных контекстов для исключения типа javax.enterprise.context.RequestScoped.

(3) Почему в моем случае использование CDI-инъекции лучше, чем использование непосредственно моего класса ConfigurationReaderDaoFactory? Если я использую свой фабричный класс напрямую, мне не нужен класс DaoQualifier, и мне не нужно выяснять процесс динамического внедрения. Меньше класса и кода всегда лучше :-)

person zappee    schedule 27.11.2014