Правильное Получение Spring Beans Из Сторонних Контекстов Приложений

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

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



Постановка задачи

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

Эта проблема может возникнуть в крупном промышленном приложении, состоящем из множества jar-файлов, каждый из которых имеет собственный контекст приложения, содержащий Spring Beans. В результате неправильной настройки мы получаем несколько копий bean-компонентов с непредсказуемым состоянием, даже если они имеют одноэлементную область видимости.

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



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

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

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



Правильное получение Spring Beans из сторонних контекстов приложений

Допустим, в контексте приложения одного из внешних модулей создан экземпляр bean-компонента класса NumberGenerator, который мы хотим получить в нашем модуле.

Предположим также, что класс NumberGenerator находится в пакете org.example.kruchon.generators, в котором хранятся все классы, генерирующие значения.



Правильное получение Spring Beans из сторонних контекстов приложений

У этого bean-компонента есть состояние — поле int count.

  
  
  
  
  
  
  
  
  
   

package org.example.kruchon.calculators public class NumberGenerator { private int count = 0; public synchronized int next() { return count++; } }

Экземпляр этого bean-компонента создается в подконфигурации GeneratorsConfiguration.

@Configuration public class GeneratorsConfiguration { @Bean public NumberGenerator numberGenerator() { return new NumberGenerator(); } .

}

Также в контексте внешнего приложения существует основная конфигурация, в которую импортируются все подконфигурации внешнего модуля.



@Configuration @Import({GeneratorsConfiguration.class, .

}) public class ExternalContextConfiguration { .

}

Теперь я приведу несколько примеров, в которых синглтон-бин класса NumberGenerator настроен неправильно в конфигурации текущего контекста приложения.



Неправильная конфигурация 1. Импорт основной конфигурации контекста внешнего приложения.

Худшее решение, которое могло быть.



@Configuration @Import(ExternalContextConfiguration.class) public class CurrentContextConfiguration { .

}

  • Приложение воссоздает все экземпляры компонентов из контекста внешнего приложения.

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

  • Мы получаем копию NumberGenerator в контексте текущего приложения.

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



Неправильная конфигурация 2. Импорт подконфигурации контекста внешнего приложения.

Второй вариант неверен и часто встречается на практике.



@Configuration @Import(GeneratorsConfiguration.class) public class CurrentContextConfiguration { .

}

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

Неправильная конфигурация 3. Найдите внедрение непосредственно в компонент, где мы хотим использовать NumberGenerator.



public class OrderFactory { private final NumberGenerator numberGenerator; public OrderFactory() { ApplicationContext externalApplicationContext = getExternalContext(); numberGenerator = externalApplicationContext.getBean(NumberGenerator.class); } public Order create() { Order order = new Order(); int id = numberGenerator.next(); order.setId(id); order.setCreatedDate(new Date()); return order; } private ApplicationContext getExternalContext(){ .

} }

В этом методе проблему дублирования bean-компонента, имеющего одноэлементную область видимости, можно считать решенной.

Ведь теперь мы повторно используем бин из другого контекста приложения и никак не создаем его заново! Но так:

  1. Усложняет разрабатываемый класс и его модульное тестирование.

  2. Предотвращает автоматическое внедрение bean-компонента класса NumberGenerator в bean-компоненты текущего модуля.

  3. В общих случаях использование LookUp для внедрения одноэлементного компонента не является общепринятой практикой.

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

Давайте посмотрим, как правильно настроить бин из контекста внешнего приложения.



Решение 1. Получите компонент из контекста внешнего приложения в конфигурации.

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



@Configuration public class CurrentContextConfiguration { @Bean public NumberGenerator numberGenerator() { ApplicationContext externalApplicationContext = getExternalContext(); return externalApplicationContext.getBean(NumberGenerator.class); } private ApplicationContext getExternalContext(){ .

} }

Теперь мы можем автоматически внедрить этот bean-компонент в bean-компоненты из нашего собственного модуля.



Решение 2. Сделайте контекст внешнего приложения родительским

Есть вероятность, что функциональность текущего модуля расширяет функциональность внешнего.

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

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

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

в конфигурации текущего контекста приложения.

Родительское соединение можно указать при создании экземпляра контекста, используя конструктор с родительским параметром:

public AbstractApplicationContext(ApplicationContext parent) { .

}

Или используйте сеттер:

public void setParent(ApplicationContext parent) { .

}

Если контекст приложения объявлен в xml, мы можем использовать конструктор:

public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { .

}



Заключение

Таким образом, будьте осторожны при настройке Spring bean-компонентов, следуйте рекомендациям, приведенным в статье, и старайтесь избегать копирования bean-компонентов, имеющих одноэлементную область видимости.

Буду рад ответить на любые ваши вопросы! Теги: #java #spring #spring beans #spring-контекст приложения #spring ioc #spring di #spring-внедрение зависимостей #дублированные Spring Singletons

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.