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