Что такое инъекция поля и как ее избежать?

130

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

@Component
public class MyComponent {
    @Autowired
    private Cart cart;
}

Во время своего исследования я также читал о внедрении конструктора :

@Component
public class MyComponent {
    private final Cart cart;

    @Autowired
    public MyComponent(Cart cart){
       this.cart = cart;
    }
}

Каковы преимущества и недостатки обоих этих типов инъекций?


РЕДАКТИРОВАТЬ 1: Поскольку этот вопрос отмечен как дубликат этого вопроса, я проверил его. Потому что нет примеров кода ни в вопросе, ни в ответах, мне непонятно, правильно ли я догадываюсь, какой тип инъекции я использую.

Т. Юнг
источник
3
Если внедрение поля - это так плохо, как вы описываете, почему Spring позволяет это? Внедрение полей имеет свои преимущества, так как делает код более читабельным и менее подробным. Если вы достаточно дисциплинированы в кодировании, вы можете быть уверены, что ничего не сломается, даже если вы используете внедрение поля.
ashes
1
@ashes Потому что в то время это была изящная функция, и последствия не были полностью продуманы. Та же причина, что и Date(int,int,int)существует.
chrylis -cautiouslyoptimistic-

Ответы:

226

Типы инъекций

Существует три варианта внедрения зависимостей в bean-компонент:

  1. Через конструктор
  2. Через сеттеры или другие методы
  3. Через отражение прямо в поля

Вы используете вариант 3. Это то, что происходит, когда вы используете его @Autowiredпрямо на своем поле.


Инъекции

Общие рекомендации, рекомендованные Spring (см. Разделы, посвященные DI на основе конструктора или DI на основе Setter ), следующие:

  • Для обязательных зависимостей или при стремлении к неизменности используйте внедрение конструктора
  • Для дополнительных или изменяемых зависимостей используйте внедрение установщика
  • В большинстве случаев избегайте полевых инъекций

Недостатки полевого ввода

Причины, по которым инъекция поля не одобряется, заключаются в следующем:

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

Вывод

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


дальнейшее чтение

Я написал в блоге статью о том, почему внедрение поля обычно не рекомендуется: Внедрение зависимости поля считается вредным .

Войтех Ружичка
источник
12
Вообще говоря, нехорошо и нехорошо говорить миру, что «следует избегать внедрения поля». Покажите плюсы и минусы и позвольте другим решать сам;) Многие люди имеют другой опыт и собственный взгляд на вещи.
Дитер
6
Это может иметь место здесь, но есть и другие случаи, когда сообщество пришло к общему консенсусу, чтобы препятствовать чему-либо. Возьмем, к примеру, венгерскую нотацию.
Jannik
Вы даете несколько хороших отзывов, например, о тестируемости и видимости зависимостей, но я не со всем согласен. Внедрение конструктора не имеет недостатков? Может быть желательно наличие 5 или 6 полей для внедрения в класс, который выполняет реальные композиции вызова. Я также не согласен с вами в неизменности. Наличие полей final не обязательно для неизменного класса. Это предпочтительнее. Что совсем другое.
davidxxx
Я думаю, вы имели в виду «Для обязательных зависимостей или при стремлении к неизменности »
Алекс Терро
1
Я имел в виду ссылку в начале ответа, которая ссылается на весеннюю документацию
Войтех Ружичка
47

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

Внедрение конструктора

Плюсы:

  • Лучшая тестируемость . Вам не нужны имитирующие библиотеки или контекст Spring в модульных тестах. Вы можете создать объект, который хотите протестировать, с новым ключевым словом. Такие тесты всегда быстрее, потому что они не полагаются на механизм отражения. ( Этот вопрос был задан через 30 минут. Если бы автор использовал инъекцию конструктора, он бы не появился).
  • Неизменяемость . После того, как зависимости установлены, их нельзя изменить.
  • Более безопасный код . После выполнения конструктора ваш объект готов к использованию, поскольку вы можете проверить все, что было передано в качестве параметра. Объект может быть готов или нет, промежуточного состояния нет. С помощью инъекции поля вы вводите промежуточный шаг, когда объект хрупкий.
  • Более четкое выражение обязательных зависимостей . Полевая инъекция в этом вопросе неоднозначна.
  • Заставляет разработчиков задуматься о дизайне . dit писал о конструкторе с 8 параметрами, что на самом деле является признаком плохого дизайна и антипаттерна объекта God . Неважно, имеет ли класс 8 зависимостей в конструкторе или в полях, это всегда неправильно. Люди более неохотно добавляют в конструктор больше зависимостей, чем через поля. Это работает как сигнал вашему мозгу, что вам следует на время остановиться и подумать о структуре кода.

Минусы:

  • Больше кода (но современные IDE облегчают боль).

В основном, закачка поля происходит наоборот.

Даниэль Ольшевский
источник
1
тестируемость, да, для меня было кошмаром издеваться над полевыми бобами. Однажды я использовал инъекцию contsructor, мне не нужно делать ненужных издевательств
kenobiwan
25

Дело вкуса. Это твое решение.

Но я могу объяснить, почему я никогда не использую внедрение конструктора .

  1. Я не хочу , чтобы реализовать конструктор для всех моих @Service, @Repositoryи @Controllerбобов. Я имею в виду, что есть около 40-50 зерен или больше. Каждый раз, если я добавляю новое поле, мне приходилось расширять конструктор. Нет, я этого не хочу и не обязана.

  2. Что делать, если вашему бину (службе или контроллеру) требуется внедрить много других бинов? Конструктор с 4+ параметрами очень уродлив.

  3. Если я использую CDI, конструктор меня не касается.


РЕДАКТИРОВАТЬ # 1 : Войтех Ружичка сказал:

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

Да. Теория и реальность. Вот пример: DashboardControllerсопоставлен с одним путем*:8080/dashboard .

мой DashboardController собирает много информации от других служб, чтобы отображать их на странице обзора панели / системы. Мне нужен этот единственный контроллер. Поэтому я должен защитить только этот единственный путь (базовый фильтр аутентификации или роли пользователя).

РЕДАКТИРОВАТЬ # 2 : Поскольку все сосредоточены на 8 параметрах в конструкторе ... Это был реальный пример - унаследованный код клиентов. Я это изменил. Та же аргументация применима ко мне для 4+ параметров.

Все дело в внедрении кода, а не в создании экземпляра.

Dieter
источник
34
Очень уродливый конструктор с 8 зависимостями на самом деле потрясающий, поскольку это красный флаг, что что-то не так, класс имеет слишком много зависимостей и, вероятно, нарушает принцип единой ответственности и должен быть переработан. На самом деле это хорошо.
Войтех Ружичка
6
@VojtechRuzicka, конечно, неприятно, но иногда этого не избежать.
Дитер
4
Я бы сказал, что эмпирическое правило из трех, не говоря уже о 40-50, зависимости для любого класса должны быть признаком того, что вам нужно провести рефакторинг. Класс с 40 зависимостями не может придерживаться принципала единственной ответственности или принципала открытия / закрытия.
Amin J
4
@AminJ Правило отличное, но реальность другая. Компания, над которой я работаю, существует более 20 лет, и у нас много устаревшего кода. Рефакторинг - хорошая идея, но стоит денег. Также я не знаю, почему это говорят, но я не имел в виду 40-50 зависимостей, я имел в виду 40-50 бинов, компонентов, модулей ...
Дитер,
7
@dit, ваша ситуация явно та, в которой технический долг заставляет вас делать неоптимальный выбор. По вашим собственным словам, вы находитесь в ситуации, когда на ваше решение существенно влияет устаревший код, которому более 20 лет. Приступая к работе над новым проектом, вы бы по-прежнему рекомендовали внедрение поля вместо внедрения конструктора? Возможно, вам стоит сделать оговорку в своем ответе, чтобы указать, в каких случаях вы бы выбрали полевое внедрение.
Умар Фарук Хаваджа
0

Еще один комментарий - Войтех Ружичка заявил, что Spring вводит beans тремя способами (ответ с наибольшим количеством баллов):

  1. Через конструктор
  2. Через сеттеры или другие методы
  3. Через отражение прямо в поля

Этот ответ НЕПРАВИЛЬНЫЙ - потому что ДЛЯ КАЖДОГО ВИДА ИНЪЕКЦИОННОЙ ПРУЖИНЫ ИСПОЛЬЗУЕТ ОТРАЖЕНИЕ! Используйте IDE, установите точку останова в установщике / конструкторе и проверьте.

Это может быть делом вкуса, но также может быть делом СЛУЧАЯ. @dieter предоставил отличный случай, когда инъекция поля лучше. Если вы используете внедрение поля в интеграционных тестах, которые настраивают контекст Spring - аргумент с тестируемостью класса также недействителен - если только вы не хотите писать позже тесты в свои интеграционные тесты;)

wujek.oczko
источник