Доступ к правильному сервису Spring только при знании типа объекта - PullRequest
0 голосов
/ 05 сентября 2018

Я построил расширение класса Tab (JavaFX), которое с помощью нескольких параметров, таких как тип класса сущности, буквально строило весь графический интерфейс вокруг класса сущности (таблица для отображения, функциональные кнопки добавления / редактирования / удаления / функциональные кнопки). поля поиска, динамически создаваемые из членов класса / и т. д.). Этот класс должен быть расширен, чтобы быть адаптированным к каждому типу сущностей, но пока у меня есть 4 сущности, использующих его, и я очень доволен результатами, он сохраняет МНОГО кода.

Одна проблема, которая у меня возникла, связана с динамическим созданием полей поиска при инициализации вкладки. Что я делаю, так это перебираю поля класса сущностей и создаю либо TextField для поиска по полю String, либо ComboBox, заполненный различными опциями, если поле имеет отношение ManyToOne с другим типом сущности. Чтобы достичь этого, я построил интерфейс маркера под названием TFHEntity, который реализуют все мои сущности. У меня также есть аннотация @HiddenField, чтобы исключить такие поля, как идентификаторы, набор для двунаправленного отображения и т. Д. См. Код ниже (обратите внимание, что имя просто для того, чтобы дать тексту приглашения французский язык, так как мои имена полей на английском):

//Create Search Fields Based on Entity Class Members and Annotations
    int i = 0;
    for (Field field : entityClass.getDeclaredFields())
    {
        if (!isFieldHidden(field))
        {
            if (isFieldEntityType(field))
            {
                FriendlyName friendlyName = field.getAnnotation(FriendlyName.class);
                ComboBox searchField = new ComboBox();
                searchField.setPromptText("Recherche " + friendlyName.value());
                searchField.setMaxWidth(Double.MAX_VALUE);
                searchField.getSelectionModel().selectedItemProperty().addListener((observable) -> 
                {
                    showEntities();
                });
                if (field.getType().getName().contains("Category"))
                {
                    ObservableList<Category> categories = FXCollections.observableArrayList(categoryService.findAll());
                    categories.add(0, new Category(null, "Recherche " + friendlyName.value(), null, null));
                    searchField.setItems(categories);
                }
                else if (field.getType().getName().contains("Supplier"))
                {
                    ObservableList<Supplier> fournisseurs = FXCollections.observableArrayList(supplierService.findAll());
                    fournisseurs.add(0, new Supplier(null, "Recherche " + friendlyName.value(), null, null, null, null, null, null));
                    searchField.setItems(fournisseurs);
                }

                searchFields.put(i, searchField);
            }
            else
            {
                FriendlyName friendlyName = field.getAnnotation(FriendlyName.class);
                TextField searchField = new TextField();
                searchField.setPromptText("Recherche " + friendlyName.value());
                searchField.setOnKeyReleased((event) -> 
                {
                    showEntities();
                });

                searchFields.put(i, searchField);
            }

            i++;
        }
    }


private boolean isFieldEntityType(Field field)
{
    for (Class c : field.getType().getInterfaces())
    {
        if (c.getName().contains("TFHEntity"))
        {
            return true;
        }
    }

    return false;
}

private boolean isFieldHidden(Field field)
{
    HiddenField hiddenField = field.getAnnotation(HiddenField.class);

    return hiddenField != null;
}

Меня беспокоит способ составления списков ObservableList для ComboBox. Я хотел бы иметь способ ДИНАМИЧЕСКИ вызывать метод findAll () нужного сервиса для заполнения этих списков, без необходимости проверять тип и повторять код, как я делаю сейчас. Я построил карту с типом класса Entity в качестве ключа и службой в качестве значения (очевидно, что все они тоже должны быть подключены автоматически), но я изо всех сил пытаюсь придумать, как этого добиться:

private final Map<Class, TFHService> serviceMap = new HashMap<>();

private void initServiceMap()
{
    serviceMap.put(Bill.class, billService);
    serviceMap.put(Category.class, categoryService);
    serviceMap.put(Customer.class, customerService);
    serviceMap.put(Parameter.class, parameterService);
    serviceMap.put(Product.class, productService);
    serviceMap.put(Sequence.class, sequenceService);
    serviceMap.put(Supplier.class, supplierService);
}

Я уверен, что рефлексия позволит мне сделать это (я знаю, что где-то есть метод вызова), но я недостаточно разбираюсь в этой теме, чтобы собрать все, что я знаю, и достичь этого. Сегодня я выучил много, у меня немного болит мозг, я очень доволен тем, чего достиг, но это ограничивает возможности расширения, поскольку мне нужно изменить суперкласс для каждого типа ComboBox.

Надеюсь, это понятно, этот код кажется мне очень сложным, и я изо всех сил пытался выразить свой вопрос словами. Пожалуйста, помогите, если вам известен способ каким-то образом вызвать метод findAll () нужного сервиса, используя то, что я создал до сих пор, и без уродливого, ограничивающего оператора if / else.

Спасибо!

1 Ответ

0 голосов
/ 06 сентября 2018

Мне удалось это выяснить! Я сделал все свои сервисные интерфейсы расширением интерфейса MasterService. Я переместил их метод findAll () в этот верхний интерфейс со следующей подписью:

List<?> findAll();

Результирующие реализации этого метода также возвращают List<?>, но соответствующий даослой, который вызывается каждым из них, знает, что возвращает правильный тип. При этом я переместил serviceMap в класс конфигурации и возвращаю его в виде bean-компонента:

@Bean
public Map<Class, TFHService> serviceMap()
{
    Map<Class, TFHService> serviceMap = new HashMap<>();

    serviceMap.put(Bill.class, billService);
    serviceMap.put(Category.class, categoryService);
    serviceMap.put(Customer.class, customerService);
    serviceMap.put(Parameter.class, parameterService);
    serviceMap.put(Product.class, productService);
    serviceMap.put(Sequence.class, sequenceService);
    serviceMap.put(Supplier.class, supplierService);

    return serviceMap;
}

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

Наконец, мой MasterService также содержит этот метод:

TFHEntity createDummy();

Все мои службы могут затем возвращать фиктивный объект только с именем (Recherche означает Поиск по-французски):

@Override
public TFHEntity createDummy() 
{
    return new Category(null, "Recherche", null, null);
}

После всего этого мой уродливый код теперь выглядит так:

int i = 0;
    for (Field field : entityClass.getDeclaredFields())
    {
        if (!isFieldHidden(field))
        {
            if (isFieldEntityType(field))
            {
                ComboBox searchField = new ComboBox();
                searchField.setPromptText("Recherche");
                searchField.setMaxWidth(Double.MAX_VALUE);
                searchField.getSelectionModel().selectedItemProperty().addListener((observable) -> 
                {
                    showEntities();
                });

                TFHService service = serviceMap.get(field.getType());
                ObservableList items = FXCollections.observableArrayList(service.findAll());
                items.add(0, service.createDummy());
                searchField.setItems(items);

                searchFields.put(i, searchField);
            }
            else
            {
                TextField searchField = new TextField();
                searchField.setPromptText("Recherche");
                searchField.setAlignment(Pos.CENTER);
                searchField.setOnKeyReleased((event) -> 
                {
                    showEntities();
                });

                searchFields.put(i, searchField);
            }

            i++;
        }
    }

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

Ура!

...