Spring @Autowire on Properties vs Constructor
Итак, поскольку я использовал Spring, если бы я должен был написать службу с зависимостями, я бы сделал следующее:
@Component
public class SomeService {
@Autowired private SomeOtherService someOtherService;
}
Теперь я запускаю код, который использует другое соглашение для достижения той же цели
@Component
public class SomeService {
private final SomeOtherService someOtherService;
@Autowired
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
Оба эти метода будут работать, я это понимаю. Но есть ли преимущество в использовании опции B? Для меня он создает больше кода в классе и unit test. (Необходимо написать конструктор и не использовать @InjectMocks)
Есть ли что-то, что мне не хватает? Есть ли еще что-то, что делает автоуведованный конструктор, помимо добавления кода в модульные тесты? Является ли это более предпочтительным способом инъекции зависимостей?
Ответы
Ответ 1
Да, вариант B (который называется инжекцией в конструктор) фактически рекомендуется по сравнению с инжекцией в поле и имеет несколько преимуществ:
- зависимости четко определены. Невозможно забыть об этом при тестировании или создании экземпляра объекта в любых других обстоятельствах (например, явное создание экземпляра компонента в классе конфигурации).
- зависимости могут быть окончательными, что помогает с надежностью и безопасностью потока
- вам не нужно отражение, чтобы установить зависимости. InjectMocks все еще можно использовать, но не обязательно. Вы можете просто создавать макеты самостоятельно и вставлять их, просто вызывая конструктор
Смотрите этот пост в блоге для более подробной статьи, написанной одним из авторов Spring, Оливье Гирке.
Ответ 2
Я объясню вам простыми словами:
В варианте (A) вы разрешаете любому (в другом классе вне/внутри контейнера Spring) создавать экземпляр с помощью конструктора по умолчанию (например, new SomeService()
), что НЕ подходит, если вам нужен объект SomeOtherService
(как зависимость) для вашего SomeService
.
Есть ли что-то еще, что делает конструктор autowired, кроме добавления кода в модульные тесты? Является ли это более предпочтительным способом внедрения зависимости?
Вариант (B) является предпочтительным подходом, так как он НЕ позволяет создавать объект SomeService
без фактического разрешения зависимости SomeOtherService
.
Ответ 3
Конструкторы Autowired
обеспечивают привязку для добавления пользовательского кода перед его регистрацией в контейнере spring. Предположим, что класс SomeService
расширяет еще один класс с именем SuperSomeService
и имеет некоторый конструктор, который принимает имя в качестве своего аргумента. В этом случае конструктор Autowired
отлично работает. Кроме того, если у вас есть некоторые другие элементы для инициализации, вы можете сделать это в конструкторе, прежде чем возвращать экземпляр в контейнер spring.
public class SuperSomeService {
private String name;
public SuperSomeService(String name) {
this.name = name;
}
}
@Component
public class SomeService extends SuperSomeService {
private final SomeOtherService someOtherService;
private Map<String, String> props = null;
@Autowired
public SomeService(SomeOtherService someOtherService){
SuperSomeService("SomeService")
this.someOtherService = someOtherService;
props = loadMap();
}
}
Ответ 4
Хорошо знать
Если есть только один вызов конструктора, нет необходимости включать аннотацию @Autowired. Тогда вы можете использовать что-то вроде этого:
@RestController
public class NiceController {
private final DataRepository repository;
public NiceController(ChapterRepository repository) {
this.repository = repository;
}
}
... пример внедрения Spring Data Repository.
Ответ 5
Собственно, по моему опыту, второй вариант лучше. Без необходимости @Autowired
. На самом деле, разумнее создавать код, который не слишком тесно связан с фреймворком (как и Spring). Вам нужен код, который максимально старается принять отложенный подход к принятию решений. Это настолько много, насколько это возможно, настолько много, что каркас можно легко заменить. Поэтому я бы посоветовал вам создать отдельный файл Config и определить там свой компонент, например:
В файле SomeService.java:
public class SomeService {
private final SomeOtherService someOtherService;
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
В файле ServiceConfig.java:
@Config
public class ServiceConfig {
@Bean
public SomeService someService(SomeOtherService someOtherService){
return new SomeService(someOtherService);
}
}
На самом деле, если вы хотите глубоко разбираться в этом, есть вопросы безопасности потоков (среди прочего), которые возникают при использовании Field Injection (@Autowired
), в зависимости от размера проекта. Проверьте это, чтобы узнать больше о преимуществах и недостатках Autowiring. На самом деле, основные ребята на самом деле рекомендуют использовать инжектор Constructor вместо Field Injection