Объясните, почему внедрение конструктора лучше, чем другие варианты

В книге Pro Spring 3, глава 4 - Введение в IOC и DI в Spring - стр. 59, в разделе «Инъекция сеттера и инъекция конструктора» в абзаце говорится

Spring, включая Spring, предоставляет механизм, обеспечивающий определение всех зависимостей при использовании Setter Injection, но с помощью Constructor Injection вы утверждаете требование для зависимости независимо от контейнера».

Не могли бы вы объяснить на примерах


person minil    schedule 19.01.2014    source источник
comment
Также stackoverflow.com/questions/39890849/   -  person Grigory Kislin    schedule 05.04.2017


Ответы (7)


Класс, который принимает требуемую зависимость в качестве аргумента конструктора, может быть создан только в том случае, если этот аргумент предоставлен (у вас должно быть защитное предложение, чтобы убедиться, что аргумент не равен нулю) (или используйте ненулевой тип в Kotlin). Таким образом, конструктор применяет требование зависимости независимо от того, используете ли вы Spring или нет, что делает его независимым от контейнера.

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

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

Обновление: Spring 4.3 будет выполнять неявное внедрение в сценариях с одним конструктором, что делает ваш код более независимым от Spring, потенциально не требуя аннотации @Autowired вообще.

person Emerson Farrugia    schedule 19.01.2014
comment
Не могли бы вы объяснить, что сеттер может вызываться или не вызываться? Если я правильно понимаю - аннотация @Autowired имеет обязательный атрибут, который имеет значение по умолчанию true - person Vahe Harutyunyan; 05.02.2017
comment
@VaheHarutyunyan это правильно, Required не единственный способ, я обновлю ответ. Но использование Autowired вместо Required по-прежнему означает, что ваш код не зависит от контейнера. - person Emerson Farrugia; 06.02.2017
comment
Также следует отметить, что внедрение конструктора может вызвать BeanCurrentlyInCreationException, если у вас есть циклическая зависимость в ваших bean-компонентах. - person Oleksandr Papchenko; 07.09.2017
comment
В чем преимущество отделения вашего класса от Spring? - person Addie; 30.04.2018
comment
@ Адди, наиболее убедительным преимуществом является то, что это упрощает тестирование. Вы можете создать экземпляр класса (и передать аргументы конструктора, например, моки) в обычном модульном тесте, без необходимости настраивать контекст приложения и сложности, связанные с настройкой этого интеграционного теста. - person Emerson Farrugia; 26.07.2020

(...) используя Constructor Injection, вы утверждаете требование для зависимости независимо от контейнера

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


Пример внедрения сеттера

При вводе сеттера требуется специальная аннотация пружины @Required.

@Required

Помечает метод (обычно метод установки JavaBean) как «обязательный»: то есть метод установки должен быть сконфигурирован для внедрения зависимостей со значением.

Использование

import org.springframework.beans.factory.annotation.Required;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class Foo {

    private Bar bar;

    @Inject
    @Required
    public void setBar(Bar bar) {
        this.bar = bar;
    }
}

Пример внедрения конструктора

Все необходимые поля определены в конструкторе, чистом Java-решении.

Использование

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class Foo {

    private Bar bar;

    @Inject
    public Foo(Bar bar) {
        this.bar = bar;
    }

}

Модульное тестирование

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

person MariuszS    schedule 19.01.2014
comment
Извините, но это отличается от моего только тем, что вы использовали аннотации. - person duffymo; 19.01.2014
comment
По делу и точно. - person Cleonjoys; 15.09.2015
comment
@duffymo да, но эта аннотация работает только в Spring, чистое решение Java работает всегда - person MariuszS; 14.09.2017

Для простоты предположим, что мы можем использовать внедрение зависимостей на основе конструктора для обязательных зависимостей и внедрение на основе сеттера для необязательных зависимостей. Это правило!!

Скажем, например.

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

С другой стороны, если у вас есть метод установки в классе POJO, вы можете или не можете установить значение для своей переменной класса, используя этот метод установки. Это полностью основано на ваших потребностях. то есть это необязательно. Поэтому, если вы передаете зависимость через метод установки класса, это неявно означает, что это необязательная зависимость. Надеюсь это понятно!!

person Jawa    schedule 13.03.2014

Внедрение конструктора используется, когда класс не может функционировать без зависимого класса.

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

В качестве конкретного примера рассмотрим ServiceRepository, работа которого зависит от IService. Поскольку ServiceRepository не может полноценно функционировать без IService, имеет смысл внедрить его через конструктор.

Тот же класс ServiceRepository может использовать Logger для трассировки. ILogger можно внедрить с помощью внедрения свойств.

Другими распространенными примерами внедрения свойств являются ICache (еще один аспект терминологии АОП) или IBaseProperty (свойство в базовом классе).

person vijayst    schedule 28.05.2015

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

Нам нужна гарантия от контейнера IoC, что перед использованием любого bean-компонента необходимо выполнить инъекцию необходимых bean-компонентов.

В стратегии внедрение установщика мы доверяем контейнеру IoC, что он сначала создаст компонент, но выполнит внедрение непосредственно перед использованием компонента с помощью методов установки. И закачка делается по вашей конфигурации. Если вы каким-то образом пропустите указание каких-либо bean-компонентов для внедрения в конфигурации, инъекция не будет выполнена для этих bean-компонентов, и ваш зависимый bean-компонент не будет функционировать соответствующим образом, когда он будет использоваться!

Но в стратегии внедрение конструктора контейнер навязывает (или должен навязывать) правильное предоставление зависимостей при создании компонента. Это было рассмотрено как "контейнер-агностический способ", так как мы должны предоставлять зависимости при создании bean-компонента, что делает видимость зависимости независимой от любого контейнера IoC.

Изменить:

Вопрос 1. И как запретить контейнеру создавать bean-компоненты конструктором со значениями null вместо отсутствующих bean-компонентов?

У вас нет возможности действительно пропустить какой-либо <constructor-arg> (в случае Spring), потому что контейнер IoC навязывает вам все аргументы конструктора, необходимые для соответствия предоставленному конструктору для создания bean-компонента. Если вы укажете null в своем <constructor-arg> намеренно. Тогда контейнер IoC ничего не может с ним сделать!

person Sazzadur Rahaman    schedule 19.01.2014
comment
У вас нет возможности действительно пропустить какой-либо <constructor-arg>, потому что Spring DI навязывает вам предоставление всех аргументов конструктора, необходимых для соответствия предоставленному конструктору для создания bean-компонента. - person Sazzadur Rahaman; 19.01.2014

Этот пример может помочь:

Класс контроллера:

@RestController
@RequestMapping("/abc/dev")
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class MyController {
//Setter Injection
@Resource(name="configBlack")
public void setColor(Color c) {
    System.out.println("Injecting setter");
    this.blackColor = c;
}

public Color getColor() {
    return this.blackColor;
}

public MyController() {
    super();
}

Color nred;
Color nblack;

//Constructor injection
@Autowired
public MyController(@Qualifier("constBlack")Color b, @Qualifier("constRed")Color r) {
    this.nred = r;
    this.nblack = b;
}

private Color blackColor;

//Field injection
@Autowired
private Color black;

//Field injection
@Resource(name="configRed")
private Color red;

@RequestMapping(value = "/customers", produces = { "application/text" }, method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.CREATED)
public String createCustomer() {
    System.out.println("Field injection red: " + red.getName());
    System.out.println("Field injection: " + black.getName());
    System.out.println("Setter injection black: " + blackColor.getName());

    System.out.println("Constructor inject nred: " + nred.getName());
    System.out.println("Constructor inject nblack: " + nblack.getName());


    MyController mc = new MyController();
    mc.setColor(new Red("No injection red"));
    System.out.println("No injection : " + mc.getColor().getName());

    return "Hello";
}
}

Цвет интерфейса:

public interface Color {
    public String getName();
}

Класс Красный:

@Component
public class Red implements Color{
private String name;

@Override
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Red(String name) {
    System.out.println("Red color: "+ name);
    this.name = name;
}

public Red() {
    System.out.println("Red color default constructor");
}

}

Класс черный:

@Component
public class Black implements Color{
private String name;

@Override
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Black(String name) {
    System.out.println("Black color: "+ name);
    this.name = name;
}

public Black() {
    System.out.println("Black color default constructor");
}

}

Конфигурационный класс для создания Bean-компонентов:

@Configuration
public class Config {

@Bean(name = "configRed")
public Red getRedInstance() {
    Red red = new Red();
    red.setName("Config red");
    return red;
}

@Bean(name = "configBlack")
public Black getBlackInstance() {
    Black black = new Black();
    black.setName("config Black");
    return black;
}

@Bean(name = "constRed")
public Red getConstRedInstance() {
    Red red = new Red();
    red.setName("Config const red");
    return red;
}

@Bean(name = "constBlack")
public Black getConstBlackInstance() {
    Black black = new Black();
    black.setName("config const Black");
    return black;
}
}

BootApplication (основной класс):

@SpringBootApplication
@ComponentScan(basePackages = {"com"})
public class BootApplication {

public static void main(String[] args) {
    SpringApplication.run(BootApplication.class, args);
}
}

Запустите приложение и нажмите URL: GET 127.0.0.1:8080/abc/dev/customers/

Output:
Injecting setter
Field injection red: Config red
Field injection: null
Setter injection black: config Black
Constructor inject nred: Config const red
Constructor inject nblack: config const Black
Red color: No injection red
Injecting setter
No injection : No injection red
person quintin    schedule 11.12.2015

С примерами? Вот простой:

public class TwoInjectionStyles {
    private Foo foo;

    // Constructor injection
    public TwoInjectionStyles(Foo f) {
        this.foo = f;
    }

    // Setting injection
    public void setFoo(Foo f) { this.foo = f; }
}

Лично я предпочитаю внедрение конструктора, когда могу.

В обоих случаях фабрика компонентов создает экземпляры TwoInjectionStyles и Foo и дает первому его зависимость Foo.

person duffymo    schedule 19.01.2014
comment
Я думаю, что OP запрашивает разницу, описанную в цитируемом тексте: используя Constructor Injection, вы утверждаете требование для зависимости независимо от контейнера - person MariuszS; 19.01.2014