Какие классы должны быть автоматически подключены Spring (когда использовать внедрение зависимостей)?

32

Я уже некоторое время использую Dependency Injection в Spring, и я понимаю, как это работает и каковы некоторые плюсы и минусы его использования. Однако, когда я создаю новый класс, я часто задаюсь вопросом - должен ли этот класс управляться Spring IOC Container?

И я не хочу говорить о различиях между аннотацией @Autowired, конфигурацией XML, внедрением сеттера, внедрением конструктора и т. Д. У меня общий вопрос.

Допустим, у нас есть сервис с конвертером:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

Очевидно, что конвертер не имеет каких-либо зависимостей, поэтому для него не требуется автоматическое подключение. Но для меня это кажется лучше, как с автопроводкой. Код чище и его легко проверить. Если я напишу этот код без DI, сервис будет выглядеть так:

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

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

В Spring MVC есть несколько хорошо известных шаблонов: контроллеры, использующие сервисы, и сервисы, использующие репозитории. Затем, если Репозиторий имеет автоматическое подключение (что обычно и происходит), то Служба также должна быть подключена автоматически. И это вполне понятно. Но когда мы используем аннотацию @Component? Если у вас есть статические классы утилит (например, конвертеры, картографы) - вы их автоматически связываете?

Вы пытаетесь сделать все классы автоматически подключенными? Тогда все зависимости класса легко внедрить (еще раз, легко понять и легко проверить). Или вы пробуете автопровод только тогда, когда это абсолютно необходимо?

Я потратил некоторое время на поиски некоторых общих правил о том, когда использовать автопроводку, но не смог найти никаких конкретных советов. Обычно люди говорят о том, «используете ли вы DI? (Да / нет)» или «какой тип внедрения зависимостей вы предпочитаете», что не отвечает на мой вопрос.

Буду благодарен за любые советы по этой теме!

Kacper86
источник
3
+1 по существу "Когда это заходит слишком далеко?"
Энди Хант
Проверьте этот вопрос и эту статью
Laiv

Ответы:

8

Согласитесь с комментариями @ ericW и просто хотите добавить, что вы можете использовать инициализаторы для сохранения компактности своего кода:

@Autowired
private Converter converter;

или

private Converter converter = new Converter();

или, если класс действительно не имеет состояния вообще

private static final Converter CONVERTER = new Converter();

Один из ключевых критериев того, должен ли Spring создавать экземпляр и внедрять bean-компонент, заключается в том, является ли этот bean-компонент настолько сложным, что вы хотите высмеять его при тестировании? Если это так, то введите его. Например, если конвертер совершает обратную передачу к какой-либо внешней системе, вместо этого сделайте его компонентом. Или, если возвращаемое значение имеет большое дерево решений с десятками возможных вариаций, основанных на вводе, то имитируйте его. И так далее.

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

обкрадывать
источник
5

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

ericW
источник
2

Мое эмпирическое правило основано на том, что вы уже сказали: тестируемость. Задайте себе вопрос: « Могу ли я легко протестировать его? » Если ответ «да», то при отсутствии какой-либо другой причины я был бы согласен с этим. Таким образом, если вы разрабатываете юнит-тест в то же время, что и вы, вы сэкономите много боли.

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

Я также предполагаю, что нет никакой причины использовать разные dto конвертеры.

Borjab
источник
0

TL; DR: гибридный подход автоматического подключения для DI и перенаправления конструктора для DI может упростить код, который вы представили.

Я смотрел на подобные вопросы из-за некоторых проблем с веб-журналом, связанных с ошибками / осложнениями при запуске Spring Framework, связанных с инициализацией бина @autowired. Я начал смешивать в другом подходе DI: пересылка конструктора. Это требует таких условий, как вы представляете («Понятно, что конвертер не имеет никаких зависимостей, поэтому для него не требуется автоматическое подключение».). Тем не менее, мне очень нравится гибкость, и она все еще довольно прямолинейна.

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

или даже как отрывок от ответа Роба

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

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

public List<CarDto> getAllCars(Converter converter) { ... }

можно сделать защищенным или закрытым для охвата только для целей тестирования / расширения.

Ключевым моментом является то, что DI не является обязательным. Предусмотрено значение по умолчанию, которое может быть переопределено прямым способом. Он имеет слабость по мере увеличения количества полей, но для 1, может быть, 2 полей я бы предпочел это при следующих условиях:

  • Очень небольшое количество полей
  • По умолчанию почти всегда используется (DI - тестовый шов) или По умолчанию почти никогда не используется (остановка пропуска), потому что мне нравится согласованность, так что это часть моего дерева решений, а не истинное ограничение дизайна

Последний пункт (немного OT, но связанный с тем, как мы решаем, что / где @autowired): класс конвертера, как он представлен, является служебным методом (никакие поля, никакой конструктор, не может быть статическим). Может быть, решение будет иметь какой-то метод mapToDto () в классе Cars? То есть подтолкнуть инъекцию преобразования к определению Cars, где, вероятно, уже тесно связаны:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
Кристиан Х
источник
-1

Я думаю, что хорошим примером этого является что-то вроде SecurityUtils или UserUtils. В любом приложении Spring, которое управляет пользователями, вам понадобится класс Util с целым набором статических методов, таких как:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (Полномочия органа)

и т.п.

и я никогда не проводил их автоматически JHipster (который я использую в качестве довольно хорошего судьи передовой практики) не делает.

Janome314
источник
-2

Мы можем разделить классы на основе функции этого класса таким образом, что контроллеры, сервис, репозиторий, сущность. Мы можем использовать другие классы для нашей логики только не для целей контроллеров, службы и т. Д., В этом случае мы могли бы аннотировать эти классы с помощью @component. Это автоматически зарегистрирует этот класс в контейнере Spring. Если он зарегистрирован, то он будет управляться контейнером Spring. Концепция внедрения зависимостей и инверсии управления может быть предоставлена ​​этому аннотированному классу.

SathishSakthi
источник
3
этот пост довольно трудно читать (стена текста). Не могли бы вы изменить его в лучшую форму?
комнат