Обзор Spring Bean
Контейнер IoC Spring управляет одним или несколькими бинами. Эти бины создаются с использованием метаданных конфигурации, которые вы предоставляете контейнеру (например, в виде определений <bean/>
в XML).
Обзор бинов
Внутри самого контейнера эти определения бинов представлены как объекты BeanDefinition, которые содержат (среди прочей информации) следующие метаданные:
- Квалифицированное имя класса пакета: обычно это фактический класс реализации определяемого бина.
- Элементы конфигурации поведения бина, которые указывают, как бин должен вести себя в контейнере (область видимости, обратные вызовы жизненного цикла и т. д.).
- Ссылки на другие бины, которые необходимы для работы данного бина. Эти ссылки также называются сотрудниками или зависимостями.
- Другие настройки конфигурации, которые необходимо установить в вновь созданном объекте, например, лимит размера пула или количество соединений, которые следует использовать в бине, управляющем пулом соединений.
В дополнение к определениям бинов, которые содержат информацию о том, как создать конкретный бин, реализации ApplicationContext также позволяют регистрировать существующие объекты, созданные вне контейнера (пользователями). Это делается путем доступа к BeanFactory ApplicationContext через метод getBeanFactory(), который возвращает реализацию DefaultListableBeanFactory. DefaultListableBeanFactory поддерживает эту регистрацию через методы registerSingleton(..) и registerBeanDefinition(..). Однако типичные приложения работают исключительно с бинами, определенными через обычные метаданные определения бинов.
Метаданные бинов и вручную предоставленные экземпляры синглтонов необходимо регистрировать как можно раньше, чтобы контейнер мог правильно обрабатывать их во время автозаполнения и других этапов интроспекции. Хотя переопределение существующих метаданных и существующих экземпляров синглтонов поддерживается в определенной степени, регистрация новых бинов во время выполнения (параллельно с активным доступом к фабрике) официально не поддерживается и может привести к исключениям параллельного доступа, несогласованному состоянию в контейнере бинов или тому и другому.
Переопределение бинов
Переопределение бина происходит, когда бин регистрируется с использованием идентификатора, который уже был выделен. Хотя переопределение бинов возможно, оно усложняет чтение конфигурации, и эта функция будет устаревать в будущих релизах.
Чтобы полностью отключить переопределение бинов, вы можете установить флаг allowBeanDefinitionOverriding в значение false в ApplicationContext до его обновления. В таком случае будет выброшено исключение, если будет использовано переопределение бина.
По умолчанию контейнер регистрирует каждое переопределение бина на уровне INFO, чтобы вы могли соответственно адаптировать свою конфигурацию. Хотя это не рекомендуется, вы можете отключить эти логи, установив флаг allowBeanDefinitionOverriding в значение true.
Java-конфигурация
Если вы используете Java-конфигурацию, соответствующий метод @Bean всегда тихо переопределяет сканируемый класс бина с тем же именем компонента, если тип возвращаемого значения метода @Bean совпадает с классом бина. Это просто означает, что контейнер будет вызывать метод фабрики @Bean в пользу любого заранее объявленного конструктора класса бина.
Именование бинов
Каждый бин имеет один или несколько идентификаторов. Эти идентификаторы должны быть уникальными в контейнере, который содержит бин. Обычно у бина есть только один идентификатор. Однако, если требуется больше одного, дополнительные могут считаться псевдонимами.
В метаданных конфигурации на основе XML вы используете атрибут id, атрибут name или оба, чтобы указать идентификаторы бинов. Атрибут id позволяет указать ровно один id. Обычно эти имена являются алфавитно-цифровыми (‘myBean’, ‘someService’ и т. д.), но они также могут содержать специальные символы. Если вы хотите ввести другие псевдонимы для бина, вы также можете указать их в атрибуте name, разделяя запятой (,), точкой с запятой (;) или пробелом. Хотя атрибут id определен как тип xsd:string, уникальность id бина обеспечивается контейнером, но не XML-парсерами.
Вы не обязаны предоставлять имя или id для бина. Если вы не укажете имя или id явно, контейнер сгенерирует уникальное имя для этого бина. Однако, если вы хотите ссылаться на этот бин по имени, используя элемент ref или поиск в стиле Service Locator, вы должны предоставить имя. Мотивации для неуказания имени связаны с использованием внутренних бинов и автозаполнением сотрудников.
Конвенции именования бинов
Конвенция заключается в использовании стандартной Java-конвенции для имен полей экземпляров при именовании бинов. То есть, имена бинов начинаются с маленькой буквы и далее записываются в camelCase. Примеры таких имен включают accountManager, accountService, userDao, loginController и т. д.
Последовательное именование бинов делает вашу конфигурацию более читаемой и понятной. Кроме того, если вы используете Spring AOP, это значительно упрощает применение советов к набору бинов, связанных по имени.
С помощью сканирования компонентов в classpath Spring генерирует имена бинов для неназванных компонентов, следуя описанным ранее правилам: по сути, берется простое имя класса и его первый символ преобразуется в строчную букву. Однако в (необычном) специальном случае, когда более одного символа и оба первых символа являются заглавными, оригинальный регистр сохраняется. Это те же правила, которые определены в java.beans.Introspector.decapitalize (которые Spring использует в данном случае).
Алиас бина вне определения бина
В самом определении бина вы можете указать более одного имени для бина, используя комбинацию из одного имени, указанного в атрибуте id, и любого количества других имен в атрибуте name. Эти имена могут быть эквивалентными алиасами для одного и того же бина и полезны в некоторых ситуациях, например, позволяя каждому компоненту в приложении ссылаться на общую зависимость, используя имя бина, специфичное для этого компонента.
Однако указание всех алиасов там, где бин фактически определен, не всегда является достаточным. Иногда необходимо ввести алиас для бина, который определен в другом месте. Это часто встречается в крупных системах, где конфигурация разделена между каждой подсистемой, и каждая подсистема имеет свой собственный набор определений объектов. В метаданных конфигурации на основе XML вы можете использовать элемент <alias/>
, чтобы достичь этого. Следующий пример показывает, как это сделать:
<alias name="fromName" alias="toName"/>
В этом случае бин (в том же контейнере) с именем fromName также может, после использования этого определения алиаса, быть упомянут как toName.
Например, метаданные конфигурации для подсистемы A могут ссылаться на DataSource под именем subsystemA-dataSource. Метаданные конфигурации для подсистемы B могут ссылаться на DataSource под именем subsystemB-dataSource. При составлении основного приложения, которое использует обе эти подсистемы, основное приложение ссылается на DataSource под именем myApp-dataSource. Чтобы все три имени ссылались на один и тот же объект, вы можете добавить следующие определения алиасов в метаданные конфигурации:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
Теперь каждый компонент и основное приложение могут ссылаться на dataSource через имя, которое уникально и гарантированно не будет конфликтовать с любым другим определением (фактически создавая пространство имен), при этом они ссылаются на один и тот же бин.
Если вы используете Java-конфигурацию, аннотация @Bean может быть использована для предоставления алиасов.
Создание бинов
Определение бина по сути является рецептом для создания одного или нескольких объектов. Контейнер обращается к рецепту для именованного бина по запросу и использует метаданные конфигурации, инкапсулированные в этом определении бина, для создания (или получения) фактического объекта.
Если вы используете метаданные конфигурации на основе XML, вы указываете тип (или класс) объекта, который должен быть создан, в атрибуте class элемента <bean/>
. Этот атрибут class (который внутренне является свойством Class в экземпляре BeanDefinition) обычно является обязательным. Вы можете использовать свойство Class одним из двух способов:
-
Обычно, чтобы указать класс бина, который будет создан в случае, если контейнер сам непосредственно создает бин, вызывая его конструктор рефлексивно, что несколько эквивалентно коду Java с оператором new.
-
Чтобы указать фактический класс, содержащий статический метод фабрики, который вызывается для создания объекта, в менее распространенном случае, когда контейнер вызывает статический метод фабрики на классе для создания бина. Тип объекта, возвращаемый при вызове статического метода фабрики, может быть тем же классом или совершенно другим классом.
Имена вложенных классов
Если вы хотите настроить определение бина для вложенного класса, вы можете использовать либо двоичное имя, либо исходное имя вложенного класса.
Например, если у вас есть класс под названием SomeThing в пакете com.example, и этот класс SomeThing имеет статический вложенный класс под названием OtherThing, они могут быть разделены знаком доллара ($) или точкой (.). Таким образом, значение атрибута class в определении бина будет com.example.SomeThing$OtherThing или com.example.SomeThing.OtherThing.
Создание с помощью конструктора
Когда вы создаете бин с помощью подхода конструктора, все обычные классы могут использоваться и совместимы с Spring. То есть класс, который разрабатывается, не должен реализовывать какие-либо специфические интерфейсы или быть закодированным определенным образом. Достаточно просто указать класс бина. Однако в зависимости от того, какой тип IoC вы используете для этого конкретного бина, вам может понадобиться конструктор по умолчанию (пустой).
Контейнер IoC Spring может управлять практически любым классом, который вы хотите, чтобы он управлял. Он не ограничен управлением истинными JavaBeans. Большинство пользователей Spring предпочитают настоящие JavaBeans с только конструктором по умолчанию (без аргументов) и соответствующими сеттерами и геттерами, смоделированными по свойствам в контейнере. Вы также можете иметь более экзотические классы, не соответствующие стилю бина, в вашем контейнере. Если, например, вам нужно использовать устаревший пул соединений, который абсолютно не соответствует спецификации JavaBean, Spring также может управлять им.
С помощью метаданных конфигурации на основе XML вы можете указать ваш класс бина следующим образом:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
Для получения подробной информации о механизме передачи аргументов в конструктор (если это необходимо) и установки свойств экземпляра объекта после его создания.
В случае аргументов конструктора контейнер может выбрать соответствующий конструктор среди нескольких перегруженных конструкторов. Тем не менее, чтобы избежать неоднозначностей, рекомендуется сохранять сигнатуры ваших конструкторов как можно более простыми.
Создание с помощью статического метода фабрики
При определении бина, который вы создаете с помощью статического метода фабрики, используйте атрибут class, чтобы указать класс, содержащий статический метод фабрики, и атрибут с именем factory-method, чтобы указать имя самого метода фабрики. Вы должны иметь возможность вызвать этот метод (с необязательными аргументами, как описано позже) и вернуть живой объект, который затем будет рассматриваться так, как если бы он был создан через конструктор. Одно из применений такого определения бина — это вызов статических фабрик в устаревшем коде.
Следующее определение бина указывает, что бин будет создан путем вызова метода фабрики. Определение не указывает тип (класс) возвращаемого объекта, а скорее класс, содержащий метод фабрики. В этом примере метод createInstance() должен быть статическим методом. Следующий пример показывает, как указать метод фабрики:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
Следующий пример показывает класс, который будет работать с предыдущим определением бина:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
В случае аргументов метода фабрики контейнер может выбрать соответствующий метод среди нескольких перегруженных методов с одинаковым именем. Тем не менее, чтобы избежать неоднозначностей, рекомендуется сохранять сигнатуры ваших методов фабрики как можно более простыми.
Типичный проблемный случай с перегрузкой методов фабрики — это Mockito с его многочисленными перегрузками метода mock. Выбирайте наиболее специфичный вариант mock, который возможен:
<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
<constructor-arg type="java.lang.String" value="clientService"/>
</bean>
Создание с использованием метода фабрики экземпляра
Аналогично созданию через статический метод фабрики, создание с помощью метода фабрики экземпляра вызывает нестатический метод существующего бина из контейнера для создания нового бина. Чтобы использовать этот механизм, оставьте атрибут class пустым, а в атрибуте factory-bean укажите имя бина в текущем (или родительском, или предковом) контейнере, который содержит экземпляр метода, который будет вызван для создания объекта. Установите имя самого метода фабрики с помощью атрибута factory-method. Следующий пример показывает, как настроить такой бин:
<!-- the factory bean, which contains a method called createClientServiceInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
Следующий пример показывает соответствующий класс:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
Один класс фабрики также может содержать более одного метода фабрики, как показывает следующий пример:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
В следующем примере показан соответствующий класс:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
Этот подход показывает, что фабричный бин сам может управляться и настраиваться через внедрение зависимостей (DI).
В документации Spring “фабричный бин” относится к бину, который настроен в контейнере Spring и который создает объекты через нестатический или статический метод фабрики. В отличие от этого, FactoryBean (обратите внимание на заглавные буквы) относится к классу реализации FactoryBean, специфичному для Spring.
Определение времени выполнения типа бина
Определить тип конкретного бина во время выполнения не так просто. Указанный класс в метаданных определения бина является лишь начальной ссылкой на класс, потенциально комбинируемой с объявленным методом фабрики или являющейся классом FactoryBean, что может привести к другому типу бина во время выполнения, или вообще не устанавливаться в случае метода фабрики на уровне экземпляра (который разрешается через указанное имя factory-bean). Кроме того, проксирование AOP может обернуть экземпляр бина интерфейсным прокси с ограниченным доступом к фактическому типу целевого бина (только его реализованным интерфейсам).
Рекомендуемый способ узнать фактический тип во время выполнения конкретного бина — это вызов BeanFactory.getType для указанного имени бина. Это учитывает все вышеперечисленные случаи и возвращает тип объекта, который вызов BeanFactory.getBean вернет для того же имени бина.