На официальном сайте мы нашли одну из его основных технологий: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();
}
}
через еду вышеСанЭксплуатация, мы успешно решили проблему с нашим двигателем. Если это ежедневное требование, мы уже можем его успешно выполнить. Но я явно не для этого пишу этот пост.
С точки зрения дизайна текущий код плох по двум причинам:
- В двух разных классах есть дубликаты
start()
метод; - нам нужно для каждого нового
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.
С отражением мы можем видеть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
метод конфигурации
- базовая конфигурация
- Автоматическая конфигурация
3.1 Базовая конфигурация на базе java
на основеjava
Ядром базовой конфигурации на самом деле являются следующие две аннотации:
-
@Configuration
: определить класс конфигурации -
@Bean
: Создаватьbean
Например, с учетом нашего ранее определенногоCar
, CombustionEngine
, Camshaft
, иCrankshaft
class, мы можем создать такой класс конфигурации:
/**
* @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
Приведены дополнительные аннотации. Хотя мы, возможно, добавили много аннотаций этого типа, есть три основных аннотации:
-
@Component
: зарегистрирован как класс, управляемый Spring -
@Autowired
: указывает Spring внедрить объект зависимости -
@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
Подход с автоматической настройкой имеет два основных преимущества:
- Требуемая конфигурация гораздо более лаконична
- Аннотации применяются непосредственно к классу, а не к классу конфигурации
Так что особых случаев нет, автоматическая настройка предпочтительнее
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 мы должны указать два атрибута:
-
id
: уникальный идентификатор компонента (эквивалентно наличию@Bean
Имя метода аннотации) -
class
: полный путь к классу (включая имя пакета)
заconstructor-arg
элемент, нам просто нужно указатьref
имущество, это существующееbean ID
цитаты. Например, конструктор элементов<constructor-arg ref="engine" />
Предоставление с удостоверением личностиengine
(определяется непосредственно вcar
фасоль) следует использовать для инъекцийcar
Компонент для конструктора компонентов.
Порядок аргументов конструктора определяется выражениемconstructor-arg
Порядок элементов определяет. Например, в определенииengine
фасоль, переданнаяCombustionEngine
Первый параметр конструктора конструктораcamshaft
bean, а второй параметр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
, и корректно внедрить все зависимости в наше приложение, но нам предстоит решить две каверзные проблемы:
- Конфликт объекта зависимости
- Циклические зависимости существуют между зависимыми объектами
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
, Я надеюсь помочь всем, спасибо.