Які класи повинні бути автоматичними провідними Spring (коли використовувати ін'єкцію залежності)?


32

Я вже деякий час використовую ін'єкційну залежність навесні, і я розумію, як вона працює, і які є плюси та мінуси її використання. Однак, коли я створюю новий клас, я часто замислююся - чи повинен цим класом керувати 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? (Так / ні)" або "який тип ін'єкції залежності ви віддаєте перевагу", що не відповідає на моє запитання.

Буду вдячний за будь-які поради щодо цієї теми!


3
+1 по суті "Коли це занадто далеко?"
Енді Хант

Ознайомтеся з цим питанням і цією статтею
Laiv

Відповіді:


8

Погодьтеся з коментарем @ ericW і просто хочу додати пам’ятати, що ви можете використовувати ініціалізатори, щоб ваш код був компактним:

@Autowired
private Converter converter;

або

private Converter converter = new Converter();

або, якщо клас справді зовсім не має стану

private static final Converter CONVERTER = new Converter();

Один з ключових критеріїв того, чи слід весною придушувати та вводити боби, - це квасоля настільки складна, що ви хочете знущатися над нею при тестуванні? Якщо так, то введіть його. Наприклад, якщо конвертер здійснює зворотну поїздку до будь-якої зовнішньої системи, тоді замість цього перетворіть його на компонент. Або якщо повернене значення має велике дерево рішень з десятками можливих варіантів на основі вхідних даних, то знущайтеся над ним. І так далі.

Ви вже добре зробили цю функціональність та її інкапсуляцію, тож тепер лише питання, чи достатньо складний, щоб вважати її окремим "блоком" для тестування.


5

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


2

Моє правило базується на тому, що ви сказали Альреді: заповідність. Запитайте себе: " Чи можу я легко перевірити це? ". Якщо відповідь "так", тоді, за відсутності будь-якої іншої причини, я був би в порядку з цим. Таким чином, якщо ви одночасно розробляєте тест на одиницю, ви збережете багато болю.

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

Я також припускаю, що взагалі немає підстав використовувати різні dto-перетворювачі.


0

TL; DR: Гібридний підхід автоматичної проводки для DI та переадресація конструктора для DI може спростити представлений вами код.

Я розглядав подібні запитання через деякі веб-журнали з помилками / ускладненнями при запуску весняної рамки, що стосуються залежностей від ініціалізації @autowired bean. Я почав змішуватися в іншому підході 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? Тобто, підштовхнути перетворення конверсії до визначення автомобілів, де, ймовірно, вже тісно пов'язане:

@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 ()

isAuthentication ()

isCurrentUserInRole (орган влади)

тощо.

і я ніколи цього не проводив автопровід. JHipster (який я використовую як досить хороший суддя найкращої практики) не робить.


-2

Ми можемо розділити класи на основі функції цього класу, такі як контролери, сервіс, сховище, сутність. Ми можемо використовувати інші класи для нашої логіки не лише з метою контролерів, сервісу тощо. З цього приводу ми могли б анотувати ці класи за допомогою @component. Це автоматично зареєструє цей клас у весняному контейнері. Якщо він зареєстрований, то ним керуватиме весняний контейнер. Концепція введення залежності та інверсія управління може бути поставлена ​​до цього анотованого класу.


3
цю публікацію досить важко читати (стіна тексту). Ви б не хотіли відредагувати його на кращу форму?
гнат
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.