Бины и область видимости — Java: Корпоративные приложения на Spring Boot
В Spring бином называется объект, который управляется, создается и настраивается Spring-контейнером. Эти объекты создаются на базе конфигурации, которая задается с помощью аннотаций. Только эти объекты участвуют в инъекции зависимостей при сборке Spring-приложения.
Конфигурация
Далее мы рассмотрим два способа создания бинов.
Создание бинов на основе классов
Если мы просто создадим класс и попытаемся внедрить его объект с помощью аннотации @Autowired , то ничего не получится. Spring никак не реагирует на обычные классы. Чтобы превратить этот класс в бин, нужно пометить его аннотацией, например:
- @Component — любой класс общего назначения, объект которого мы хотим получить в приложении
- @Repository — репозитории
- @RestController — контроллеры
Изучим пример с репозиторием:
package io.hexlet.spring.repository; import hexlet.code.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository // Во время компиляции этот интерфейс превращается в конкретный класс public interface UserRepository extends JpaRepositoryUser, Long> >
Внедрение происходит так:
@RestController @RequestMapping("/users") public class UserController @Autowired private UserRepository userRepository; >
Создание бинов на основе методов
В реальных проектах внедряться могут не только объекты классов, реализованные программистом. Еще можно внедрять объекты классов, которые находятся в библиотеках. Например, есть известная библиотека datafaker , которая используется в тестах для генерации данных. Работает она так:
import net.datafaker.Faker; var faker = new Faker(); var name = faker.name().fullName(); // Miss Samanta Schmidt var firstName = faker.name().firstName(); // Emory var lastName = faker.name().lastName(); // Barton var streetAddress = faker.address().streetAddress(); // 60018 Sawayn Brooks Suite 449
Существует два основных способа использования этой библиотеки внутри Spring Boot. Первый – это создание объекта напрямую в том месте, где мы хотим его использовать. В примере с Faker мы будем создавать и использовать объект внутри теста:
@SpringBootTest @AutoConfigureMockMvc public class UsersControllerTest @BeforeEach public void setUp() var faker = new Faker(); // Тут создаем нужные данные >
Второй – это создание бина с помощью метода. Для этого нам нужно создать метод внутри любого класса, помеченного аннотацией @Configuration . Проще всего это сделать в классе с методом main , потому что аннотация @SpringBootApplication автоматически добавляет аннотацию @Configuration :
@SpringBootApplication public class Application public static void main(String[] args) SpringApplication.run(Application.class, args); > @Bean public Faker getFaker() // Имя метода не важно return new Faker(); > >
Теперь Faker можно внедрять как обычную зависимость:
@SpringBootTest @AutoConfigureMockMvc public class UsersControllerTest @Autowired private Faker faker; @BeforeEach public void setUp() // Тут создаем нужные данные >
Жизненный цикл бинов
У бинов есть понятие жизненного цикла, что позволяет встраиваться в процесс их создания и уничтожения. Делается это с помощью аннотаций методов @PostConstruct и @PreDestroy внутри класса нужного бина:
import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; @Component public class MyBean private String message; @PostConstruct public void init() this.message = "Bean is initialized!"; System.out.println(message); > @PreDestroy public void cleanup() System.out.println("Cleaning up resources or performing final actions!"); > // . other methods . >
Типичные ситуации, когда это бывает нужно:
- Чтение конфигурации и инициализация некоторых свойств
- Установка ресурсов, таких как соединение с базой данных
- Регистрация бинов во внешних системах
Область видимости бинов
Область видимости бинов определяет жизненный цикл и саму видимость бинов внутри контекста приложения. Другими словами, она определяет, сколько объектов создается и как они переиспользуются разными частями приложения. Всего существует шесть областей видимости.
По умолчанию используется область Singleton. Бины с такой областью создаются ровно один раз за все время существования приложения. Каждая инъекция такого бина использует один и тот же объект.
Область Prototype означает, что новый бин будет создан на каждый запрос (инъекцию):
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.stereotype.Component; @Scope("prototype") @Component public class PrototypeBean <>
Область Request означает, что новый бин создается на каждый HTTP-запрос. Актуально только для веб-приложений:
import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @RequestScope @Component public class RequestScopedBean <>
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Области видимости Singleton и Prototype в Spring
Управление осуществляется только одним общим экземпляром бина-одиночки, а все запросы к бинам с идентификатором или идентификаторами, соответствующими определению этого бина, приводят к тому, что контейнер Spring возвращает именно этот экземпляр бина.
Иными словами, когда вы назначаете определение бина и он входит в область видимости singleton, IoC-контейнер Spring создает ровно один экземпляр объекта, назначенного этим определением бина. Этот единственный экземпляр хранится в кэше таких бинов-одиночек, а все последующие запросы и ссылки на этот именованный бин возвращают кэшированный объект. На следующем изображении показано, как работает область видимости singleton:
Понятие бина-одиночки в Spring отличается от понятия шаблона-одиночки, определенного в книге о шаблонах «Банды четырёх» (Gang of Four/GoF). В случае объекта-одиночки GoF область видимости объекта жестко кодируется таким образом, что для каждого ClassLoader создается один и только один экземпляр конкретного класса. Область видимости одиночки в Spring лучше всего описать как per-container и per-bean. Это означает, что если вы определяете один бина для конкретного класса в одном контейнере Spring, контейнер Spring создает один и только один экземпляр класса, заданного этим определением бина. Область видимости singleton — это область видимости Spring по умолчанию. Чтобы определить бин как объект-одиночку в XML, вы можете определить бина способом, показанным в следующем примере:
Область видимости prototype
Не-singleton область видимости prototype для развертывания бина создает новый экземпляр бина каждый раз, когда выполняется запрос на этот конкретный бин. То есть бин внедряется в другой бин или запрашивается через вызов метода getBean() в контейнере. Как правило, следует использовать область видимости prototype для всех бинов, сохраняющих состояние, и область видимости singleton для бинов, не сохраняющих состояние.
Следующая схема иллюстрирует область видимости prototype в Spring:
(Объект доступа к данным (DAO) обычно не конфигурируется как прототип, потому что типичный DAO не сохраняет никакого диалогового состояния. Было проще повторно использовать ядро схемы для объекта-одиночки).
В следующем примере бин определяется как прототип в XML:
В отличие от других областей видимости, Spring не управляет полным жизненным циклом бина-прототипа. Контейнер создает экземпляр, конфигурирует и иным образом компонует объект-прототип и передает его клиенту, без дальнейших записей об этом экземпляре прототипа. Таким образом, хотя методы обратного вызова жизненного цикла инициализации вызываются для всех объектов независимо от области видимости, в случае прототипов, сконфигурированные обратные вызовы жизненного цикла разрушения не вызываются. Клиентский код должен подчищать объекты, входящие в область видимости prototype, и высвободить ценные ресурсы, которые потребляют бины-прототипы. Чтобы заставить контейнер Spring высвободить ресурсы, потребляемые бинами, входящими в область видимости prototype, попробуйте использовать специальный постпроцессор бинов, содержащий ссылку на бины, которые необходимо подчистить.
В некотором смысле роль контейнера Spring в отношении бинов, входящих в область видимости prototype, является заменой оператора Java new . Все управление жизненным циклом после данного момента должно осуществляться с клиентской стороны. (Подробнее о жизненном цикле бина в контейнере Spring см. в разделе Обратные вызовы жизненного цикла).
Бины-одиночки с зависимостями прототип-бин
Если вы используете бины, находящиеся в области видимости singleton, и с зависимостью от бинов-прототипов, помните, что зависимости разрешаются при создании экземпляра. Таким образом, если внедрять зависимость от бина-прототипа в бин, находящийся в области видимости singleton, создается экземпляр нового бина-прототипа, а затем внедряется в бин-одиночку. Экземпляр прототипа — это единственный экземпляр, который предоставляется бину, находящемуся в области видимости singleton.
Однако, предположим, вам нужно, чтобы бин, находящийся в области видимости singleton, неоднократно получал новый экземпляр бина, находящегося в области видимости prototype, во время выполнения. У вас не получится внедрить зависимость от бина, находяшегося в области видимости prototype, в ваш бин-одиночку, потому что внедрение происходит только один раз, когда контейнер Spring создает экземпляр бина-одиночки, разрешает и внедряет его зависимости.
Области видимости Request, Session, Application, и WebSocket в Spring
Области видимости request , session , application и websocket доступны, только если вы используете реализацию ApplicationContext в фреймворке Spring с поддержкой веб (например, XmlWebApplicationContext ). Если вы используете эти области видимости с обычными IoC-контейнерами Spring, такими как ClassPathXmlApplicationContext , будет сгенерирован IllegalStateException c сообщением о неизвестной области видимости бина.
Начальная веб-конфигурация
Для поддержки создания области видимости бинов на уровне request , session , application и websocket (web-scoped бины) перед определением бина требуется провести небольшую начальную настройку. (Данная начальная настройка не требуется для стандартных областей видимости: singleton и prototype ).
То, каким образом вы выполните эту начальную настройку, зависит от вашей конкретной среды сервлетов.
Если вы обращаетесь к бинам, находящимся в области видимости, в Spring Web MVC, то есть в рамках запроса, который обрабатывается Spring DispatcherServlet , никакой специальной настройки не требуется. DispatcherServlet уже раскрывает все соответствующие состояния.
Если вы используете веб-контейнер Servlet 2.5, в котором запросы обрабатываются вне Spring DispatcherServlet (например, при использовании JSF или Struts), вам необходимо зарегистрировать org.springframework.web.context.request.RequestContextListener ServletRequestListener . Для Servlet 3.0+ это можно сделать программно, используя интерфейс WebApplicationInitializer . В качестве альтернативы или для более старых версий контейнеров добавьте следующее объявление в файл web web.xml вашего веб-приложения:
. org.springframework.web.context.request.RequestContextListener .
В качестве альтернативы, если есть проблемы с настройкой слушателя, рассмотрите возможность использования RequestContextFilter в Spring. Отображение фильтров зависит от окружающей конфигурации веб-приложения, поэтому вы должны изменить его соответствующим образом. В следующем листинге показана часть фильтра веб-приложения:
. requestContextFilter org.springframework.web.filter.RequestContextFilter requestContextFilter /* .
DispatcherServlet , RequestContextListener и RequestContextFilter делают одно и то же, а именно связывают объект HTTP-запроса с Thread , который обслуживает этот запрос. Это позволяет получить доступ к бинам, входящим в область видимости request или session, далее по цепочке вызовов.
Область видимости request
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина LoginAction с помощью определения бина loginAction для каждого HTTP-запроса. То есть бин loginAction находится в области видимости на уровне HTTP-запроса. Можно сколько угодно изменять внутреннее состояние создаваемого экземпляра, поскольку другие экземпляры, созданные на основе того же определения бина loginAction , не видят этих изменений состояния. Они зависят от конкретного запроса. Если запрос завершает обработку, бин, находящийся в области видимости запроса, исключается.
При использовании компонентов, управляемых аннотациями, или конфигурации Java, аннотацию @RequestScope можно использовать для назначения компонента области видимости request . В следующем примере показано, как это сделать:
@RequestScope @Component public class LoginAction < // . >
@RequestScope @Component class LoginAction < // . >
Область видимости в пределах сеанса
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина UserPreferences с помощью определения бина userPreferences на время жизни одной HTTP Session . Иными словами, бин userPreferences эффективно располагается в области видимости на уровне HTTP Session . Как и в случае с бинами, находящимися в области видимости request, вы можете изменять внутреннее состояние созданного экземпляра сколько угодно, зная, что другие экземпляры HTTP Session , которые также используют экземпляры, созданные на основе того же определения бина userPreferences , не заметят этих изменений состояния, поскольку они относятся к отдельной HTTP Session . Если HTTP Session в конечном итоге исключается, бин, который привязан к этой конкретной HTTP Session , также исключается.
При использовании компонентов, управляемых аннотациями, или конфигурации Java можно использовать аннотацию @SessionScope , чтобы назначить компонент области видимости session .
@SessionScope @Component public class UserPreferences < // . >
@SessionScope @Component class UserPreferences < // . >
Область видимости в пределах приложения
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина AppPreferences , используя определение бина appPreferences единожды для всего веб-приложения. То есть бин appPreferences находится в области видимости на уровне ServletContext и хранится как обычный атрибут ServletContext . Он в некоторой степени похож на бин-одиночку Spring, но отличается от него двумя важными особенностями: Он является объектом-одиночкой для каждого ServletContext , а не для Spring ApplicationContext (которых может быть несколько в любом конкретном веб-приложении), и он фактически открывается, поэтому и виден как атрибут ServletContext .
При использовании компонентов, управляемых аннотациями, или конфигурации Java можно использовать аннотацию @ApplicationScope для назначения компонента области видимости application . В следующем примере показано, как это сделать:
@ApplicationScope @Component public class AppPreferences < // . >
@ApplicationScope @Component class AppPreferences < // . >
Область видимости в пределах WebSocket
Область видимости WebSocket связана с жизненным циклом сессии WebSocket и применяется к приложениям STOMP over WebSocket.
Руководство по Spring. Конфигурирование с помощью Java.
В предыдущих постах мы уже рассмотрели конфигурацию в Spring с помощью XML-файлов.
Но стоит упомянуть, что в Spring Framework поддерживается конфигурация с помощью Java, что временами бывает удобно. Это позволяет нам настроить большую часть Spring-приложения без использования конфигурационного файла XML, используя специальные аннотации.
В конфигурации с помощью аннотаций Java, ключевыми являются @Configuration и @Bean
@Configuration
Эта аннотация, прописанная перед классом, означает, что класс может быть использован контейнером Spring IoC как конфигурационный класс для бинов.
@Bean
Аннотация @Bean, прописанная перед методом, информирует Spring о том, что возвращаемый данным методом объект должен быть зарегистрирован, как бин.
В простейшем случае, это может выглядеть так, как показано в простом приложении, приведённом ниже.
Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.
Результат работы программы
Но это всего лишь простейший случай. Если же нам необходимо внедрить какую-либо зависимость, то это будет выглядеть так, как показано в приложении, приведённом ниже.
Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.
Результат работы программы
И в заключение приведём простые пример того, как должны выглядеть классы, если мы хотим настроить область видимости (scope) класса с помощью Java-аннотаций.
Настройка области видимости (scope) бина:
В этой статье мы ознакомились с основами конфигурации Spring-приложения с помощью Java-аннотаций.