Общий тип Spring Inject

Скажем, у нас есть такой интерфейс:

public interface Validator<T> {
  boolean isValid(T data);
}

И это часть основного модуля. Несколько приложений могут использовать один и тот же основной модуль с другим значением общего T. Пример реализации (из определенного модуля приложения):

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean isValid(String data) {
    return false;
  }
}

А затем в контроллере (который является частью основного модуля):

@RestController
public class ValidateController {
  @Autowired
  private Validator validator;

  @RequestMapping("/")
  public void index() {
    validator.validate("");
  }
}

IntelliJ жалуется, что я использую необработанные типы; как видите, на самом деле я делаю это в контроллере.

Мой вопрос: есть ли способ ввести зависимость ограниченным образом (вместо инъекции Validator, инъекции Validator<String>)? Но, конечно, связанный тип может меняться в зависимости от приложения, использующего основной модуль?

Если это невозможно (вероятно, из-за стирания типа), как лучше всего это сделать? Просто использовать Object? Нет ли лучшей альтернативы, которая по-прежнему обеспечивает безопасность типов?

Я где-то видел людей, говорящих, что можно творить магию во время компиляции, чтобы изменить типы, но я не уверен, как это сделать, или даже правильно ли я это прочитал?

Я использую Spring, поэтому я надеюсь, что Spring сможет мне чем-то помочь! Немного магии приветствуется!

"Какая наилучшая практика для этого?" ... введите то, что вам нужно. Вам нужен AppValidator не какой-то случайный валидатор, который может что-то сделать со строкой.

Tom 28.06.2019 00:52

@ Том На практике да. Но здесь ОП сделал контроллер общим компонентом. Это используется приложениями, а не наоборот.

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

Ответы 2

Ответ прост: никакой магии не нужно, она просто работает в Spring. У вас есть свой AppValidator, а затем вы просто делаете (он вводится путем просмотра универсального типа):

@Autowired 
private Validator<String> appValidator;

Ну да ладно, а теперь представьте, что у вас их два Validator<String>, что тогда? required a single bean but found two exception - вот что. Вот почему это ужасная практика, никогда не делайте этого.

На моей работе один человек создал общий интерфейс с 3 универсальными типами, а затем на основе этих универсальных типов внедрил, люди до сих пор его ненавидят. Это выглядело так, и да, это работает, если у вас нет одних и тех же 3 универсальных типов в одном и том же порядке в нескольких реализациях:

@Autowired
private Invoker<String, Integer, Person> personInvoker;

@Autowired
private Invoker<Integer, String, Animal> animalInvoker;

Даже если в вашем коде нет нескольких Validator<String> и вы не планируете их добавлять, кто-то еще может прийти и добавить их, или во многих других сценариях.

В этих случаях явное наименование компонента и использование @Qualifier в том месте, где он внедрен, уточняет, какой экземпляр компонента следует использовать.

Andrew S 28.06.2019 01:43

Да, и люди обычно идут на это решение вместо того, чтобы исправить свой плохой дизайн. Пример из моего ответа сделал это также. Было 49 реализаций этого интерфейса, и у них было несколько перекрывающихся универсальных типов — @Qualifier("stringIntegerPersonInvoker") на помощь

Shadov 28.06.2019 02:06

Может я не так объяснил, но в данном случае вы колете Validator<String>, а это не совсем то, что я хочу? Некоторым другим приложениям может понадобиться Validator<Integer>, а другому приложению может понадобиться что-то еще; это переменная, я не могу ее жестко запрограммировать. По сути, у меня может быть два разных приложения, использующих одну и ту же кодовую базу, но одно может проверяться с помощью String, а другое — с Integer. Мне нужно, чтобы ядро ​​​​было абстрагировано от этого?

Will 28.06.2019 08:51
Ответ принят как подходящий

Вот отношения между вашими модулями (приложениями и ядром):

Application 1       Application 2      Application 3
     |                   |                   |
Validator<Foo>     Validator<Bar>     Validator<FooBar>
     |                   |                   |  
     |                   |                   |  
     |__ __ __ __ __ __ _| __ __ __ __ __ __ | 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController  (not generic rest controller)                   

Что-то здесь не так, так как вы хотите, чтобы общий компонент ValidateController опирался на конкретный общий Validator класс приложения, но ValidateController не является универсальным классом, поэтому вы можете придерживаться только Object в качестве универсального типа, где вы будете использовать поле Validator.
Чтобы сделать вещи последовательными, вы должны создать эту отсутствующую ссылку. На самом деле вам нужны отдельные подклассы контроллера, потому что каждый контроллер должен использовать определенный экземпляр валидатора. Например, вы можете определить абстрактный класс/интерфейс ValidateController в общем модуле/коде и оставить каждый подкласс расширять его и определять для себя общий Validator класс для использования.

Здесь целевые отношения между вашими модулями:

Application 1        Application 2        Application 3
     |                   |                      |
Validator<Foo>       Validator<Bar>       Validator<FooBar>
FooController(bean)  BarController(bean)  FooBarController(bean)
     |                   |                      |  
     |                   |                      |  
     |__ __ __ __ __ ___ | __ ___ __ __ __ __ __| 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController<T>  (abstract class and not a bean)                   

Например, в основном/общем модуле:

public abstract class ValidateController<T> {

  private Validator<T> validator;

  ValidateController(Validator<T> validator){
     this.validator = validator;
  }

  @RequestMapping("/")
  public void index(T t) {
    boolean isValid = validator.validate(t);
  }

}

В приложении определите свою реализацию валидатора:

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean validate(String data) {
    return ...;
  }
}

И определите также подкласс StringController (или @Bean в качестве альтернативы), чтобы установить правильный Validator :

@RestController
public class StringController extends ValidateController<String>{

   public ValidateControllerApp(Validator<String> validator){
       this.validator = validator;
   }

}

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

Will 28.06.2019 08:54

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

davidxxx 28.06.2019 09:04

Я принял ваш ответ, большое спасибо. Еще один вопрос, если вы не возражаете: есть ли способ легко создать этот шаблон для вас? Я знаю, что это просто простой класс, расширяющий другой, но тогда у меня может быть служба, которая также является универсальной (поэтому мне придется создать базовую службу...). Есть ли способ сделать это «автоматически»? Большое спасибо

Will 28.06.2019 09:09

Пожалуйста. У вас есть несколько способов. Например, вы можете полагаться на ту же стратегию, что и JpaRepository, которая освобождает вас от объявления любого конструктора. Обратите внимание, что использование этой стратегии требует некоторого времени для реализации: она не предоставляется из коробки. Но вы не могли бы пойти дальше с точки зрения сокращения плиты котла, не сделав свой дизайн непрозрачным.

davidxxx 28.06.2019 09:12

Понял. Большое спасибо за вашу помощь, я одобрил ее и добавил +1 к вашему ответу. Теперь я вернусь и буду работать над этим! :)

Will 28.06.2019 09:13

Пожалуйста. Рад, если смог помочь вам на вашем пути :) Увидимся

davidxxx 28.06.2019 09:15

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