Статья для понимания Spring Dependency Injection

Spring

在这里插入图片描述

На официальном сайте мы нашли одну из его основных технологий:Dependency Injection, именуемый:DI, что переводится каквнедрение зависимости. Сегодня у нас будет такая тарелка.

В этой статье мы углубимся вSpringРамкаDIИстория, стоящая за ним, в том числеSpring Inversion of Control(Инверсия контроля),DIиApplicationContextинтерфейс. Основываясь на этих основных понятиях, мы рассмотрим, как использоватьjavaи на основеXMLконфигурация для созданияSpringприменение. Наконец, мы рассмотрим, как создатьSpringНекоторые распространенные проблемы, возникающие при работе с приложением, в том числебобовый конфликтиЦиклические зависимости.

Одна инверсия управления

изучениеDIПрежде давайте научимсяIoC(Инверсия контроля), следующий абзац может заставить вас чувствовать себя более многословным при чтении, но внимательно изучите цель каждого изменения и наше решение, посколькупонять контрольИнверсия очень важна.

Сначала давайте разберемся, как мы обычно создаем экземпляр объекта. В обычное время мы используемnewКлючевое слово создает экземпляр объекта. Например, если естьCarкласс, мы можем создать экземпляр объекта, используяCar

Car car = new Car();

Поскольку автомобиль состоит из многих частей, мы определяемEngineинтерфейс для имитации автомобильного двигателя, а затемengineОбъекты размещаются как переменные-членыCarсвоего рода

public interface Engine {
     void turnOn();
}

public class Car {

    private Engine engine;
    
    public Car() {}
    
    public void start() {

        engine.turnOn();

    }

}

Можем ли мы теперь вызвать метод start()? Очевидно, нет, с первого взгляда будет сообщеноNullPointerException (NPE), потому что мы неCarинициализируется в конструктореengine. Обычно решение, которое мы используем, состоит в том, чтобы использовать его в конструкторе Car.Engineкакая реализация интерфейса, и назначить эту реализацию непосредственноengineполе;

Теперь давайте сначала создадимEngineКласс реализации интерфейса

public class ElectricEngine implements Engine {
    @Override
    public void turnOn() {
        System.out.println("电动引擎启动");
    }
}

public class CombustionEngine implements Engine {
    @Override
    public void turnOn() {
        System.out.println("燃油引擎启动");
    }
}

Мы модифицируем конструктор Car для использованияElectricEngineосознать, что нашengineПоля назначаются экземпляруElectricEngineобъект

public class Car {

    private Engine engine;

    public Car() {
        this.engine = new ElectricEngine();
    }

    public void start() {

        engine.turnOn();

    }

    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

Теперь мы выполняемstart()метод, мы увидим следующий вывод:

在这里插入图片描述

все готово, мы решилиNPE(нулевой указатель) проблема, но мы победили?Хахаха, видимо нет!

Решая задачу, мы ввели еще одну проблему. Хотя мы абстрагируемсяEngineинтерфейс, а затем через другойEngineРеализация классов, отвечающих за бизнес-логику различных типов движков, действительно является хорошей стратегией проектирования. ноосторожныйпартнеры, возможно, обнаружили, что мыCarконструктор классаengineобъявлен какCombustionEngine, что приведет квсе машиныУ всех бензиновый двигатель. Если бы мы сейчас создавали другой автомобильный объект с электрическим двигателем, нам пришлось бы изменить наш дизайн. Более распространенный подход заключается в создании двух отдельных классов, каждый из которых выполняет свою роль, в своих конструкторах.engineназначен наEngineРазличные реализации интерфейса;

Например:

public class CombustionCar {
    
    private Engine engine;
    
    public CombustionCar() {
        this.engine = new CombustionEngine();
    }
    
    public void start() {
        engine.turnOn();
    }

}

public class ElectricCar {
    private Engine engine;

    public ElectricCar() {
        this.engine = new ElectricEngine();
    }
    
    public void start() {
        engine.turnOn();
    }
    
}

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

С точки зрения дизайна текущий код плох по двум причинам:

  1. В двух разных классах есть дубликатыstart()метод;
  2. нам нужно для каждого новогоEngineКласс реализации создает новый класс;

Особенно последнюю проблему решить труднее, потому что мы не контролируемEngineреализация, эта проблема будет усугубляться, поскольку разработчики продолжают создавать свои собственные классы реализации;

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

Мы можем создать родительский классCar,будетобщедоступный кодИзвлечение его в родительский класс может легко решить первую проблему. так какEngineПоля приватные, мы в родительском классеCarполучил в конструктореEngineобъект и присвоить его.

public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.turnOn();
    }
}

public class CombustionCar extends Car{

    public CombustionCar() {
        super(new CombustionEngine());
    }

}

public class ElectricCar extends Car {

    public ElectricCar() {
        super(new ElectricEngine());
    }

}

Таким образом, мы успешно решилидублирование кодапроблема, давайте проверим это:

public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.turnOn();
    }

    public static void main(String[] args) {

        CombustionCar combustionCar1 = new CombustionCar();
        combustionCar1.start();
        ElectricCar electricCar1 = new ElectricCar();
        electricCar1.start();
    }
}

在这里插入图片描述

Так как же нам ответить на второй вопрос, который мы подняли?

На самом деле можно посмотреть на этот вопрос и под другим углом: почему мы должны обращать на это внимание?CombustionCarиElectricCar, теперь мы вернемся к нашемуCar, теперь мы разрешили клиенту создавать экземплярыCarобъект будетEngineОбъект передается в качестве параметра конструктора, что фактически устраняет необходимость в каждомEngineобъект создать новыйCarПроблема. Потому что сейчасCarкласс зависит отEngineинтерфейс, ничего не знаетEngineреализация;

через сEngineконструктор параметров, который мы собираемся использоватьEngineРешение о реализации отCarСам класс (изначально созданныйCombustionEngineПринять решение)изменить насоздавать экземплярCarкласс клиентов. Такое обращение процесса принятия решений называетсяIoC原则. Теперь клиент должен контролировать, какая реализация используется, а неCarСам класс контролирует, что используетсяEngineвыполнить.

Это немного сбивает с толку, давайте объединим следующий пример кода и тщательно обдумаем его.

public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.turnOn();
    }

    public static void main(String[] args) {

        /**
         * 老法子
         * 为每一类型发送机的车创建类,然后实现父类car,然后在构造函数传入自己的引擎,然后调用start()
         */
        CombustionCar combustionCar1 = new CombustionCar();
        combustionCar1.start();
        ElectricCar electricCar1 = new ElectricCar();
        electricCar1.start();

        /**
         * 控制反转思想
         * 把自己看作实例化car的客户端,需要什么引擎,直接传入相关对象
         */
        CombustionEngine combustionEngine = new CombustionEngine();
        Car combustionCar = new Car(combustionEngine);
        combustionCar.start();
        ElectricEngine electricEngine = new ElectricEngine();
        Car electricCar = new Car(electricEngine);
        electricCar.start();
    }
}

Выполнив приведенный выше код, мы обнаружили, что можем получить желаемые результаты:

在这里插入图片描述

Как видно из приведенного выше примера, создание экземпляраCarКлиенты класса могут управлятьEngineреализации и зависит от того, какойEngineреализация переданаCarКонструктор,CarПоведение объекта резко меняется. Почему вы так говорите, смотрите ниже

Внедрение двух зависимостей (внедрение зависимостей)

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

Первоначально мы создаем экземплярCarПараметр не требуется, потому что он уже предоставлен нам в его конструктореnewохватыватьEngineобъект. использоватьIoCметод, мы просим, ​​чтобы при создании экземпляраCarПеред этим нам нужно создатьEngineобъект и передать его в качестве параметра вCarПостроить объект. Другими словами, изначально мы сначала создаем экземплярCarобъект, затем создайте экземплярEngineобъект. Однако, используяIoCПосле этого мы сначала создаем экземплярEngineобъект, затем создайте экземплярCarобъект;

Поэтому мы создали зависимость в вышеуказанном процессе. Однако эта зависимость не относится ко времени компиляции.Carпара классовEngineЗависимости интерфейса, вместо этого мы вводим зависимость времени выполнения. Во время выполнения создайте экземплярCarобъект должен быть сначала созданEngineобъект.

2.1 Дерево зависимостей

Конкретный зависимый объект можно понимать как bean-компонент в Spring.Для двух зависимых bean-компонентов зависимый bean-компонент называется зависимым объектом.

Смотрим зависимости между ними графически, где узлы графа представляют объекты, а стрелки — зависимости (стрелки указывают на зависимые объекты). для нас мойCarДля классов дерево зависимостей очень простое:

еслидерево зависимостейТерминальный узел имеет свои дополнительные зависимости, то этодерево зависимостейстанет сложнее. Теперь, глядя на наш пример выше, еслиCombustionEngineЕсть и другие зависимые объекты, нам сначала нужно создатьCombustionEngineзависимые объекты перед созданием экземпляраCombustionEngineобъект. Это создаетCarобъект,CombustionEngineПерейти кCarконструктор;

//凸轮轴		
public class Camshaft {}
//机轴
public class Crankshaft {}

public class CombustionEngine implements Engine {

  //凸轮轴
  private Camshaft camshaft;

  //机轴
  private Crankshaft crankshaft;

  public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {

      this.camshaft = camshaft;

      this.crankshaft = crankshaft;
  }

  @Override

  public void turnOn() {

      System.out.println("燃油引擎启动");

  }

}

После нашего преобразования наше текущее дерево зависимостей становится следующим

2.2 Платформа внедрения зависимостей

Эта сложность будет продолжать расти, поскольку мы продолжаем вводить больше зависимостей. Чтобы решить эту сложную проблему, нам нужно извлечь процесс создания объекта на основе дерева зависимостей. ЭтоПлатформа внедрения зависимостей.

В целом, мы можем разделить этот процесс на три части:

  1. Объявляет, какие зависимые объекты необходимы для создания объекта
  2. Зарегистрируйте классы, необходимые для создания этих зависимых объектов.
  3. Предоставляет механизм для создания объектов с использованием идей 1 и 2.

С отражением мы можем видетьCarконструктор класса и знает, что ему нуженEngineпараметр. Итак, чтобы создать объект Car, нам нужно создать хотя бы одинEngineКласс реализации интерфейса используется как зависимость. Здесь мы создаемCombustionEngineобъект (Ради удобства он временно считается только одним классом реализации, а проблема конфликта компонентов будет обсуждаться позже.), чтобы объявить его для использования в качестве зависимости, он удовлетворяетCarТребования при создании объекта.

На самом деле этот процесс является рекурсивным, посколькуCombustionEngineВ зависимости от других объектов нам нужно повторять первый процесс до тех пор, пока не будут объявлены все зависимые объекты, а затем зарегистрировать классы, необходимые для создания этих зависимых объектов.

Третий пункт — фактически реализовать на практике первые две идеи для формирования механизма создания объектов.

Например: скажем, нам нуженCarобъект, мы должны пройти по дереву зависимостей и проверить, есть ли хотя бы один подходящий класс, который удовлетворяет всем зависимостям. Например, объявитьCombustionEngineкласс удовлетворяетEngineТребования к узлу. Если такая зависимость существует, мы создаем экземпляр зависимости и переходим к следующему узлу.

Если более чем один класс удовлетворяет требуемой зависимости, то мы должны явно объявить, какой из них следует выбрать. Мы обсудим, как Spring делает это позже.

Как только мы убедимся, что все зависимости готовы, мы можем начать создавать объекты зависимостей с конечного узла. заCarобъект, мы сначала создаем экземплярCamshaftиCrankshaftーーпотому что эти объекты не имеют зависимостей ーーзатем передайте эти объекты вCombustionEngineконструктор для создания экземпляраCombunstionEngineобъект. Наконец, мы будемCombunstionEngineобъект переданCarконструктор для создания экземпляра желаемогоCarобъект.

понялDIПосле обоснования , мы можем теперь перейти к обсуждениюSpringКак выполнятьDI.

2.3 Внедрение зависимостей Spring

SpringЯдро представляет собойDIкаркас, который можетDIКонфигурация преобразуется вJavaприменение.

Здесь мы должны ответить на вопрос: чтоРазница между библиотеками и фреймворками. Библиотека — это просто набор определений классов. Причиной этого является просто повторное использование кода, то есть получение кода, который уже написали другие разработчики. Эти классы и методы обычно определяют определенные операции в предметно-ориентированных областях. Например, существуют математические библиотеки, которые позволяют разработчикам просто вызывать функции, не переделывая реализацию того, как работает алгоритм.

Фреймворк обычно рассматривается как скелет, в который мы вставляем код для создания приложения. Многие фреймворки сохраняют разделы для конкретных приложений и требуют от нас, разработчиков, предоставления кода, соответствующего фреймворку. На практике это означает написание реализации интерфейса и последующую регистрацию реализации в фреймворке.

2.4 ApplicationContext

существуетSpring, рамка окружаетApplicationContextИнтерфейс реализует три изложенных в предыдущем разделеDIобязанность. Обычно этот интерфейс представляет контекст. Поэтому мы опиралисьjavaили на основеxmlконфигурация дляApplicationContextзарегистрируйте соответствующий класс и начнитеApplicationContextсоздание запросаbeanобъект. потомApplicationContextПостройте дерево зависимостей и пройдите его, чтобы создать необходимыеbeanобъект

ApplicationcontextСодержащуюся в нем логику часто называютSpringконтейнер. ОбычноSpringПриложения могут иметь несколькоApplicationContext, каждыйApplicationContextМогут быть отдельные конфигурации. Например,ApplicationContextможет быть настроен на использованиеCombustionEngineкак реализация его движка, в то время как другой контейнер может быть настроен на использованиеElectricEngineкак его реализация.

В этой статье мы сосредоточимся на индивидуальномApplicationContext, но концепции, описанные ниже, используются даже тогда, когда приложение имеет несколькоApplicationContextТакже относится к экземплярам.

Три конфигурации на основе Java

Весна дает нам дваjavaметод конфигурации

  1. базовая конфигурация
  2. Автоматическая конфигурация

3.1 Базовая конфигурация на базе java

на основеjavaЯдром базовой конфигурации на самом деле являются следующие две аннотации:

  1. @Configuration: определить класс конфигурации
  2. @Bean: Создаватьbean

Например, с учетом нашего ранее определенногоCar, CombustionEngine, Camshaft, иCrankshaftclass, мы можем создать такой класс конфигурации:

/**
 * @author milogenius
 * @date 2020/5/17 20:52
 */
@Configuration
public class AnnotationConfig {
    
    @Bean
    public Car car(Engine engine) {
        return new Car(engine);
    }

    @Bean
    public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
        return new CombustionEngine(camshaft, crankshaft);
    }

    @Bean
    public Camshaft camshaft() {
        return new Camshaft();
    }

    @Bean
    public Crankshaft crankshaft() {
        return new Crankshaft();
    }
}

Далее мы создаемApplicationContextобъект, изApplicationContextобъект получаетCarобъекта, а затем в созданномCarвызов на объектstartметод:

ApplicationContext context = 

    new AnnotationConfigApplicationContext(AnnotationConfig.class);

Car car = context.getBean(Car.class);

car.start();

Результат выполнения следующий:

Started combustion engine

Несмотря на то что@Configurationи@BeanКомбинация аннотацийSpringпредоставляет достаточно информации для внедрения зависимостей, но нам по-прежнему нужно вручную определить каждую из них, которая будет внедрена.beanи явно объявить их зависимости. Чтобы уменьшить конфигурациюDIНакладные расходы, требуемые фреймворком, Spring обеспечиваетjavaавтоматическая конфигурация.

3.2 автоматическая настройка на основе Java

Для поддержки на основеjavaавтоматическая настройкаSpringПриведены дополнительные аннотации. Хотя мы, возможно, добавили много аннотаций этого типа, есть три основных аннотации:

  1. @Component: зарегистрирован как класс, управляемый Spring
  2. @Autowired: указывает Spring внедрить объект зависимости
  3. @ComponentScan: инструктирует Spring, где искать@ComponentАннотированный класс

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

@AutowiredАннотации используются для руководстваSpring, где мы намерены внедрить объект зависимости, в котором используется аннотация. Например, вCarВ конструкторе мы ожидаем внедритьEngineобъект, поэтому мы даемCarДобавлен конструктор@Autowiredаннотация. используя@Componentи@AutowiredАннотация меняет насCarкласс следующим образом:

@Component
public class Car {

  private Engine engine;

 
  @Autowired

  public Car(Engine engine) {

      this.engine = engine;
  }

  public void start() {

      engine.turnOn();

  }

}

Мы можем повторить этот процесс в других классах:

@Component

public class Camshaft {}

@Component
public class Crankshaft {}


@Component

public class CombustionEngine implements Engine {

 

 private Camshaft camshaft;

 private Crankshaft crankshaft;


 @Autowired

 public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {

     this.camshaft = camshaft;

     this.crankshaft = crankshaft;

 }


 @Override

 public void turnOn() {

     System.out.println("Started combustion engine");

 }

}

После преобразования связанных классов нам нужно создать@Configurationкласс для руководстваSpringКак автоматически настроить наше приложение. на основеjavaБазовая конфигурация, которую мы явно указываемSpringкак пользоваться@BeanАннотацию создает каждый бин, но в автоконфигурации мы прошли@Componentи@AutowiredАннотации предоставляют достаточно информации о том, как создать все необходимые bean-компоненты. Единственная недостающая информацияSpringгде искать наш@Componentаннотированный класс и зарегистрировать его как соответствующий bean-компонент.

@ ComponentscanАннотация содержит параметрbasePackages, что позволяет нам указать имя пакета какString,Springбудет найден рекурсивным поиском@Componentсвоего рода. В нашем примере пакетcom.milo.domain, поэтому класс конфигурации, который мы получаем:

@Configuration
@ComponentScan(basePackages = "com.milo.domain")
public class AutomatedAnnotationConfig {}

ApplicationContext context = 

    new AnnotationConfigApplicationContext(AutomatedAnnotationConfig.class);

Car car = context.getBean(Car.class);   

car.start();

Результаты:

Started combustion engine

через и на основеjavaСравнивая базовую конфигурацию, мы обнаружили, что на основеjavaПодход с автоматической настройкой имеет два основных преимущества:

  1. Требуемая конфигурация гораздо более лаконична
  2. Аннотации применяются непосредственно к классу, а не к классу конфигурации

Так что особых случаев нет, автоматическая настройка предпочтительнее

3.2.2 Внедрение поля

В дополнение к внедрению конструктора мы также можем внедрять напрямую через поля. мы можем поставить@AutowiredЭто достигается применением аннотации к нужному полю:

@Component
public class Car {


  @Autowired

  private Engine engine;

  

  public void start() {

      engine.turnOn();

  }

}

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

3.2.3 Инъекция сеттера

Последней альтернативой внедрению конструктора является внедрение сеттера, где@AutowiredАннотация применяется к установщику, связанному с полем. Например, мы можем изменитьCarКласс, полученный путем внедрения сеттераEngineобъект, используется метод@AutowiredаннотацияsetEngineметод:

@Component
public class Car {


  private Engine engine;

  

  public void start() {

      engine.turnOn();

  }


  public Engine getEngine() {

      return engine;

  }


  @Autowired

  public void setEngine(Engine engine) {

      this.engine = engine;

  }

}

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

Четыре конфигурации на основе xml

Другой метод настройки основан наxmlКонфигурация. мы вXMLопределяется в конфигурационном файлеbeanи их взаимосвязь, затем указатьSpringГде найти наши файлы конфигурации.

Первым шагом является определениеbean. Мы в основном следуем и основываемся наjavaТе же шаги для базовой конфигурации, но с использованиемxmlbeanэлемент вместо этого. существуетXMLслучае мы также должны явно заявить, что намерены использоватьconstructor-argЭлементы, внедренные в другие конструкторыbean. комбинироватьbeanиconstructor-argэлементы, получаем следующееXMLКонфигурация:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns:util="http://www.springframework.org/schema/util"

  xsi:schemaLocation="

      http://www.springframework.org/schema/beans

      http://www.springframework.org/schema/beans/spring-beans.xsd

      http://www.springframework.org/schema/util

      http://www.springframework.org/schema/util/spring-util.xsd">


  <bean id="car" class="com.milo.domain.Car">

      <constructor-arg ref="engine" />

  </bean>

  

  <bean id="engine" class="com.milo.CombustionEngine">

      <constructor-arg ref="camshaft" />

      <constructor-arg ref="crankshaft" />

  </bean>

  

  <bean id="camshaft" class="com.milo.Camshaft" />

  <bean id="crankshaft" class="com.milo.Crankshaft" />


</beans>

В элементе bean мы должны указать два атрибута:

  1. id: уникальный идентификатор компонента (эквивалентно наличию@BeanИмя метода аннотации)
  2. class: полный путь к классу (включая имя пакета)

заconstructor-argэлемент, нам просто нужно указатьrefимущество, это существующееbean IDцитаты. Например, конструктор элементов<constructor-arg ref="engine" />Предоставление с удостоверением личностиengine(определяется непосредственно вcarфасоль) следует использовать для инъекцийcarКомпонент для конструктора компонентов.

Порядок аргументов конструктора определяется выражениемconstructor-argПорядок элементов определяет. Например, в определенииengineфасоль, переданнаяCombustionEngineПервый параметр конструктора конструктораcamshaftbean, а второй параметрcrankshaftбобы.

ПолучатьApplicationContextобъект, нам просто нужно изменитьApplicationContextТип реализации. потому что мы будемXMLФайл конфигурации находится в пути к классам, поэтому мы используемClassPathXmlApplicationContext:

ApplicationContext context = 

    new ClassPathXmlApplicationContext("basic-config.xml");

Car car = context.getBean(Car.class);

car.start();

Результаты:

Started combustion engine

Пять часто задаваемых вопросов

Теперь мы выяснили, как работает среда Spring.DI, и корректно внедрить все зависимости в наше приложение, но нам предстоит решить две каверзные проблемы:

  1. Конфликт объекта зависимости
  2. Циклические зависимости существуют между зависимыми объектами

5.1 Наличие нескольких подходящих зависимых объектов

на основеjavaи на основеxmlспособ, который мы указалиSpringиспользовать толькоCombustionEngineкак нашEngineвыполнить. если мы будемElectricEngineЗарегистрируйтесь как совместимыйdiЧто происходит со стандартными компонентами? Чтобы проверить результаты, мы изменимjavaпример автоконфигурации и использования@ComponentаннотацияElectricEngineсвоего рода:

@Component
public class ElectricEngine implements Engine {


  @Override

  public void turnOn() {

      System.out.println("Started electric engine");

  }

}

Если мы повторно запустим приложение автоконфигурации на основе Java, мы увидим следующую ошибку:

No qualifying bean of type 'com.dzone.albanoj2.spring.di.domain.Engine' available: expected single matching bean but found 2: combustionEngine,electricEngine

Поскольку мы аннотировали@ComponentвыполнитьEngineДва класса интерфейсов, а именноCombustionEngineиElectricEngineーーspringНевозможно определить сейчас при создании экземпляраCarКакой из этих двух классов следует использовать для удовлетворенияEngineзависимости. Чтобы решить эту проблему, мы должны явно указатьSpringиспользуйте эти дваbeanкоторый из .

5.1.1 Аннотация @Qualifier

Один из способов — назвать наши зависимые объекты и использовать их в приложении.@AutowiredИспользуйте там, где аннотировано@QualifierАннотация для определения зависимого объекта для внедрения. так,@QualifierАннотации квалифицируют bean-компоненты, которые автоматически внедряются, уменьшая количество bean-компонентов, удовлетворяющих требованию, до одного. Например, мы можем назвать нашуCombustionEngineЗависимые объекты:

@Component("defaultEngine")
public class CombustionEngine implements Engine {

    

    // ...代码省略,未改变

}

Тогда мы можем добавить@QualifierАннотация, имя которой соответствует имени зависимого объекта, который мы хотим внедрить, чтобы мыEngineобъект вCarавтоматически внедряется в конструктор

@Component
public class Car {

  

  @Autowired

  public Car(@Qualifier("defaultEngine") Engine engine) {

      this.engine = engine;

  }

  

  // ...existing implementation unchanged...

}

Если мы перезапустим наше приложение, мы больше не получим предыдущую ошибку:

Started combustion engine

Обратите внимание, что если класс без явного имени bean-компонента имеет имя по умолчанию, именем по умолчанию является имя класса с первой буквой нижнего регистра. Например, нашCombusttionengineИмя класса по умолчаниюcombusttionengine

5.1.2 @ Основная аннотация

Если мы знаем, что предпочитаем реализацию по умолчанию, мы можем отказаться@Qualifierзаметьте, прямо@PrimaryАннотации добавляются в класс. Например, мы можем преобразовать нашCombusttionengine,ElectricEngineиCarИзмените класс на:

@Component
@Primary

public class CombustionEngine implements Engine {

  

   // ...existing implementation unchanged...

}

@Component
public class ElectricEngine implements Engine {

  

    // ...existing implementation unchanged...

}


@Component
public class Car {

 

 @Autowired

 public Car(Engine engine) {

     this.engine = engine;

 }

 

 // ...existing implementation unchanged...

}

Мы перезапускаем наше приложение и получаем следующий вывод:

Started combustion engine

Это доказывает, что хотя есть две возможности удовлетворитьEngineзависимости, т.е.CombustionEngineиElectricengine,ноSpringможет в соответствии с @PrimaryАннотации определяют, какую из двух реализаций следует использовать в первую очередь.

5.2 Циклические зависимости

Хотя мы подробно обсудилиSpring DIосновы, но остается один важный вопрос, оставшийся без ответа: что произойдет, если дерево зависимостей имеет циклическую ссылку? Например, предположим, что мы создаемFooкласс, конструктор которого требуетBarобъект, ноBarКонструктор требуетFooобъект.

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

@Component
public class Foo {



  private Bar bar;


  @Autowired

  public Foo(Bar bar) {

      this.bar = bar;

  }

}


@Component
public class Bar {



 private Foo foo;


 @Autowired

 public Bar(Foo foo) {

     this.foo = foo;

 }

}

Затем мы можем определить следующую конфигурацию:

@Configuration
@ComponentScan(basePackageClasses = Foo.class)
public class Config {}

Наконец, мы можем создать нашApplicationContext:

ApplicationContext context = 

    new AnnotationConfigApplicationContext(Config.class);

Foo foo = context.getBean(Foo.class);

Когда мы выполняем этот фрагмент кода, мы видим следующую ошибку:

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?

Во-первых,Springпопробуй создатьFooобъект. В этом процессеSpringпризнать необходимостьBarобъект. чтобы построитьBarобъект, требуетсяFooобъект. так какFooОбъект находится в стадии строительства (поэтому был создан объект Bar),springПомните, что циклические ссылки могут встречаться.

Одним из самых простых решений этой проблемы является использование класса и точки внедрения@Lazyаннотация. Это указывает Spring откладывать аннотированные bean-компоненты и аннотированные@AutowiredИнициализация локации. Это позволяет успешно инициализировать один из компонентов, тем самым разрывая циклическую цепочку зависимостей. Понимая это, мы можем изменитьFooиBarсвоего рода:

@Component
public class Foo {

  

  private Bar bar;


  @Autowired

  public Foo(@Lazy Bar bar) {

      this.bar = bar;

  }

}


@Component

@Lazy

public class Bar {


  @Autowired

  public Bar(Foo foo) {}

}

При использовании@LazyПовторно запустите приложение после аннотации, и никаких сообщений об ошибках обнаружено не было.

Шесть резюме

В этой статье мы исследуемSpringбазовые знания, в том числеIoC,DIиSpring ApplicationContext. Затем мы вводим использованиеjavaконфигурации и на основеxmlсоздание конфигаSpringБазовые знания о приложении при изучении использованияSpring DIНекоторые распространенные проблемы, с которыми вы можете столкнуться. Хотя поначалу эти концепции могут показаться неясными,SpringКод несвязный, но мы можем распознать его по нижнему слою.Spirng, Я надеюсь помочь всем, спасибо.