Понимание того, как Spring работает за кулисами - Глава II

Приятно снова вас видеть! Если вы впервые посещаете мой блог на Medium, это вторая часть серии о том, как Spring Framework работает за кулисами. Возможно, вы захотите взглянуть на первую главу, объясняющую основы контекста IoC и Spring:



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

  • В контексте Spring хранятся все объекты, которые будут использоваться позже во время выполнения приложения.
  • IoC - это принцип, в котором каждый объект определяет свои зависимости.
  • Spring использует аннотации, такие как @Configuration и @Bean, чтобы узнать, какой объект необходимо сохранить.

Итак, вопрос на миллион долларов: как эти сохраненные объекты теперь будут использоваться?

Получение объектов из контекста

Первое, что вам нужно знать, это то, что Spring совсем не волшебный и не знает, где вы собираетесь использовать bean-компоненты, хранящиеся в контексте Spring. Вам нужно сказать ему, где вы хотите сделать этот укол. Давайте использовать более подходящий термин: вам нужно указать Spring, какой объект вы хотите связать с контекстом.

Итак, помня основы Java, первое, что нам нужно, это переменная, представляющая наш желаемый объект:

private StoredBean storedBean;

У этой переменной еще нет экземпляра, поэтому, если вы запустите код, он будет иметь NullPointerException. Теперь нам нужно связать эту переменную с существующим экземпляром в контексте. Если вы помните последнюю главу, у вас уже должен быть класс @Configuration, экземпляр которого хранится в контексте как @Bean:

@Configuration 
class MyConfigClass {   @Bean
   public StoredBean storedBean() {
      return new StoredBean() // Here is our instance
   }
}

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

@Autowired
private StoredBean storedBean;

И теперь у вас есть первая переменная, связанная с экземпляром контекста Spring. Теперь о нескольких вещах, о которых следует помнить:

  • Правильный термин - не проводка, это инъекция. Вы должны быть знакомы с настоящим.
  • Это только начало, продолжайте читать;)

Что произойдет, если у меня есть две переменные с @Autowired?

Помня основы Java, у нас есть только один экземпляр в контексте, поэтому обе переменные будут нацелены на один и тот же объект:

Но, как я уже говорил, весна - это вовсе не волшебство. Как Spring идентифицирует совпадающий объект в контексте? Теперь начинается интересное:

Когда вы создаете себя @Bean, у вас есть 2 варианта:

  • Дайте имя на уровне аннотации:
@Bean(name="storedBean")
public StoredBean storedBean() {
    return new StoredBean() // Here is our instance
}
  • Определите имя на уровне сигнатуры метода (просто storedBean())

В обоих случаях связанное имя в контексте Spring будет storedBean. Наконец, Spring найдет подходящий экземпляр на основе следующих критериев:

  • Соответствующий тип объекта. Ищите переменные того же типа в контексте Spring.
  • Ищем @Qualifier в случае, если в контексте существуют 2 компонента одного типа.
  • По имени фасоли.

На примере все понятно, правда? Это очень распространенный сценарий в реальном мире, представьте, что у вас есть приложение с несколькими базами данных:

@Bean
public DatabaseConnection databaseA() {
   return new DatabaseConnection(connectionParams)
}   
@Bean
public DatabaseConnection databaseB() {
   return new DatabaseConnection(otherConnectionParams)
}

И имя вашей переменной:

@Autowired
private DatabaseConnection database;

Итак, отлаживая процесс, Spring будет:

1- Ищите объект того же типа. У нас есть два экземпляра DatabaseConnection, поэтому Spring не угадает, какой из них правильный. Следующий шаг.

2- Найдите @Qualifier имя. Мы его еще не предоставили, так что следующий шаг.

3- Найдите подходящее имя bean-компонента. database! = databaseA или databaseB.

Тогда Spring выдаст исключение: NoUniqueBeanDefinitionException

А теперь давайте исправим эту ошибку. У нас есть 2 варианта:

  • Измените имя переменной с database на databaseA или databaseB (в зависимости от того, какое нам нужно)
  • Более элегантно используйте @Qualifier:
@Autowired
@Qualifier("databaseA")
private DatabaseConnection database;

Теперь Spring будет знать, что нам действительно нужно. Интересно, правда? Продолжайте читать, это еще не все.

Есть третий способ позволить Spring выбрать для вас подходящий боб. @Profile аннотация очень полезна, когда вы работаете с разными средами, например, databaseDev и databaseProd - частые случаи.

Правильное внедрение зависимостей

Как вы можете заметить, до сих пор мы использовали аннотацию @Autowired только на уровне переменных. Это полезно для академических целей, но может вызвать проблемы в реальных условиях. У нас есть 2 альтернативных способа внедрения зависимостей:

Уровень конструктора

Это наиболее рекомендуемый способ внедрения зависимостей по следующим причинам:

  • Правильное применение IoC. Мы определяем зависимости объектов на уровне конструктора, поэтому мы не можем пропустить ни одну из них.
  • Легко для тестирования. Вы можете создавать макеты из ваших экземпляров контекста в модульных тестах и ​​внедрять их через конструктор.
private DatabaseConnection database;@Autowired // Constructor level
public DatabaseService(DatabaseConnection database) {
    this.database = database;
}

Уровень сеттера

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

private DatabaseConnection database;@Autowired // Setter level
public setDatabaseConnection(DatabaseConnection database) {
   this.database = database;
}public DatabaseService() { // No dependencies here
}

Кроме того, при тестировании вы по-прежнему можете вводить макеты через сеттер.

Итак, зачем прекращать использование инъекций на переменном уровне?

Так как:

  • Небезопасно от отсутствия зависимостей
  • Действительно сложно проверить. Вы не сможете внедрить какой-либо макет в созданный объект.

Если вы не знакомы с тестированием и имитированием, я рекомендую вам прочитать эту статью, в которой объясняются основы тестирования:



@ Bean scope

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

  • singleton: Область по умолчанию, создающая один экземпляр для всех переменных.
  • prototype: Создает новый экземпляр при каждой ссылке на компонент.
  • session: новый экземпляр создается в зависимости от сеанса пользователя (только в веб-среде).
  • request: новый экземпляр создается в соответствии с сделанным запросом (только в веб-среде)

Например, мы могли бы изменить область видимости нашего bean-компонента databaseA, просто добавив аннотацию @Scope:

@Bean
@Scope("prototype")
public DatabaseConnection databaseA() {
   return new DatabaseConnection(connectionParams)
}

Теперь новый экземпляр DatabaseConnection будет создаваться каждый раз при обращении к databaseA.

Некоторые заключительные рекомендации для этой главы:

  • Читайте о весенних стереотипах:


  • Теперь вы знаете, как работает внедрение зависимостей, посмотрите, как внедрить внешнюю конфигурацию из файлов:


Https://www.baeldung.com/spring-value-defaults

Мы закончили вторую часть серии Spring Core, которую я выпускаю в этом месяце. Если вы сочтете это полезным, подпишитесь на меня и следите за обновлениями следующих глав:

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