Скажем, у нас есть такой интерфейс:
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 сможет мне чем-то помочь! Немного магии приветствуется!
@ Том На практике да. Но здесь ОП сделал контроллер общим компонентом. Это используется приложениями, а не наоборот.




Ответ прост: никакой магии не нужно, она просто работает в 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 в том месте, где он внедрен, уточняет, какой экземпляр компонента следует использовать.
Да, и люди обычно идут на это решение вместо того, чтобы исправить свой плохой дизайн. Пример из моего ответа сделал это также. Было 49 реализаций этого интерфейса, и у них было несколько перекрывающихся универсальных типов — @Qualifier("stringIntegerPersonInvoker") на помощь
Может я не так объяснил, но в данном случае вы колете Validator<String>, а это не совсем то, что я хочу? Некоторым другим приложениям может понадобиться Validator<Integer>, а другому приложению может понадобиться что-то еще; это переменная, я не могу ее жестко запрограммировать. По сути, у меня может быть два разных приложения, использующих одну и ту же кодовую базу, но одно может проверяться с помощью String, а другое — с Integer. Мне нужно, чтобы ядро было абстрагировано от этого?
Вот отношения между вашими модулями (приложениями и ядром):
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;
}
}
Вау, я думаю, это ответ. Позвольте мне попробовать. По сути, вы говорите, что каждое приложение должно расширять фактический контроллер и указывать реальный тип. Великий; позвольте мне быстро попробовать
Это именно так. Вам нужен отдельный подкласс контроллера, потому что каждый контроллер должен использовать определенный экземпляр валидатора.
Я принял ваш ответ, большое спасибо. Еще один вопрос, если вы не возражаете: есть ли способ легко создать этот шаблон для вас? Я знаю, что это просто простой класс, расширяющий другой, но тогда у меня может быть служба, которая также является универсальной (поэтому мне придется создать базовую службу...). Есть ли способ сделать это «автоматически»? Большое спасибо
Пожалуйста. У вас есть несколько способов. Например, вы можете полагаться на ту же стратегию, что и JpaRepository, которая освобождает вас от объявления любого конструктора. Обратите внимание, что использование этой стратегии требует некоторого времени для реализации: она не предоставляется из коробки. Но вы не могли бы пойти дальше с точки зрения сокращения плиты котла, не сделав свой дизайн непрозрачным.
Понял. Большое спасибо за вашу помощь, я одобрил ее и добавил +1 к вашему ответу. Теперь я вернусь и буду работать над этим! :)
Пожалуйста. Рад, если смог помочь вам на вашем пути :) Увидимся
"Какая наилучшая практика для этого?" ... введите то, что вам нужно. Вам нужен
AppValidatorне какой-то случайный валидатор, который может что-то сделать со строкой.