Как уже упоминалось в предыдущем разделе, вы можете определить свойства бина и аргументы конструктора как ссылки на другие управляемые бины (взаимодействующие объекты) или как встроенные значения. Конфигурационные метаданные Spring на основе XML поддерживают для этой задачи типы подэлементов в элементах <property/> и <constructor-arg/>.

Прямые значения (примитивы (primitives), строки (stringes) и т.д.)

Атрибут value элемента <property/> определяет свойство или аргумент конструктора в виде удобного для восприятия человеком строкового представления. Служба преобразования Spring используется для преобразования этих значений из String в реальный тип свойства или аргумента. В следующем примере показаны различные задаваемые значения:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- приводит к вызову setDriverClassName(String) -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

В следующем примере используется пространство имен p-namespace для еще более лаконичной конфигурации XML:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>
</beans>

Предыдущий XML более лаконичен. Однако ошибки обнаруживаются во время выполнения, а не во время проектирования, если только вы не используете интегрированную среду разработки (IDE) (например, IntelliJ IDEA или Spring Tools для Eclipse), которая поддерживает автоматическое заполнение свойств при создании определений бинов. Настоятельно рекомендуется пользоваться таким вспомогательным функционалом IDE.

Вы также можете настроить экземпляр java.util.Properties следующим образом:

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <!-- типизировано как java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Контейнер Spring преобразует текст внутри элемента <value/> в экземпляр java.util.Properties с помощью механизма PropertyEditor JavaBeans. Это хорошее сокращение, и это один из немногих случаев, в которых команда Spring отдает предпочтение использованию вложенного элемента <value/>, а не стилю атрибута value.

Элемент idref

Элемент idref - это просто безошибочный способ передать id (строковое значение - не ссылку) другого бина в контейнере в элемент <constructor-arg/> или <property/>. В следующем примере показано, как его использовать:

<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

Предыдущий фрагмент кода определения бина в точности эквивалентен (во время выполнения) следующему фрагменту:

<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

Первая форма предпочтительнее второй, поскольку использование тега idref позволяет контейнеру проверять во время развертывания, что именованный бин, на который дается ссылка, действительно существует. Во втором варианте проверка значения, передаваемого в свойство targetName бина client, не производится. Ошибки обнаруживаются (скорее всего, с критическими последствиями) только во время создания экземпляра client бина. Если client бин является prototype бином, эта ошибка и возникающее исключение можно обнаружить только спустя долгое время после развертывания контейнера.

Атрибут local в элементе idref больше не поддерживается в XSD бинов версии 4.0, поскольку он больше не предоставляет значения по сравнению с обычной ссылкой на bean. При переходе на схему 4.0 измените существующие ссылки idref local на idref bean.

Распространенное место (по крайней мере, в версиях, предшествующих Spring 2.0), где элемент <idref/> возвращает значение, является конфигурация АОП-перехватчиков (interceptors) в определении бина ProxyFactoryBean. Использование элементов <idref/> при указании имен перехватчиков предотвращает неправильное написание идентификатора перехватчика.

Ссылки на другие бины (взаимодействующие объекты)

Элемент ref является конечным элементом внутри элемента определения <constructor-arg/> или <property/>. Здесь вы задаёте значение указанного свойства бина как ссылку на другой бин (взаимодействующий объект), управляемый контейнером. Бин, на который дается ссылка, является зависимостью бина, свойство которого нужно задать, и он инициализируется по требованию, по мере необходимости, до того, как свойство будет задано. (Если взаимодействующий объект является бином-одиночкой, он может быть уже инициализирован контейнером). Все ссылки в конечном итоге являются ссылками на другой объект. Определение области видимости и валидация зависят от того, задаете вы ID или имя другого объекта через атрибут bean или parent.

Указание целевого бина через атрибут bean тега <ref/> является наиболее общей формой и позволяет создать ссылку на любой бин в том же контейнере или родительском контейнере, независимо от того, находится ли он в том же XML-файле. Значение атрибута bean может совпадать с атрибутом id целевого бина или быть таким же, как одно из значений в атрибуте name целевого бина. В следующем примере показано, как использовать элемент ref:

<ref bean="someBean"/>

Задание целевого бина через атрибут parent создает ссылку на бин, который находится в родительском контейнере текущего контейнера. Значение атрибута parent может совпадать либо с атрибутом id целевого бина, либо с одним из значений в атрибуте name целевого бина. Целевой бин должен находиться в родительском контейнере текущего. Следует использовать этот вариант ссылки на бин в основном тогда, когда имеется иерархия контейнеров и вы хотите обернуть существующий бин в родительском контейнере прокси, который имеет то же имя, что и родительский бин. Следующая пара листингов показывает, как использовать атрибут parent:

<!-- в родительском контексте -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- вставляем сюда необходимые зависимости -->
</bean>
<!-- в дочернем (потомственном) контексте-->
<bean id="accountService" <!-- имя бина совпадает с именем родительского бина -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- обратите внимание, как мы ссылаемся на родительский бин -->
    </property>
    <!-- вставляем сюда другие конфигурации и зависимости по мере необходимости -->
</bean>
Атрибут local в элементе ref больше не поддерживается в XSD бинов версии 4.0, поскольку он больше не предоставляет значения по сравнению с обычной ссылкой на bean. При переходе на схему 4.0 измените существующие ссылки ref local на ref bean.

Внутренние бины

Элемент <bean/> внутри элементов <property/> или <constructor-arg/> содержит определение внутреннего бина, как показано в следующем примере:

<bean id="outer" class="...">
    <!-- вместо использования ссылки на целевой бин, просто даем определение целевому встроенному бину -->
    <property name="target">
        <bean class="com.example.Person"> <!-- это внутренний бин -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

Определение внутреннего бина не требует определенного ID или имени. Если задано, контейнер не будет использовать такое значение в качестве идентификатора. Контейнер также игнорирует флаг scope при создании, поскольку внутренние бины всегда анонимны и всегда создаются вместе с внешним бином. Невозможно получить доступ к внутренним бинам независимо или внедрить их во взаимодействующие бины, кроме как в объемлющий бин.

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

Коллекции

Элементы <list/>, <set/>, <map/>, и <props/> задают свойства и аргументы типов Collection List, Set, Map и Properties на основе Java, соответственно. В следующем примере показано, как их использовать:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- приводит к вызову setAdminEmails(java.util.Properties) -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- приводит к вызову setSomeList(java.util.List) -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- приводит к вызову setSomeMap(java.util.Map) -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- приводит к вызову setSomeSet(java.util.Set) -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

Значением ключа или значением в ассоциативных массивах, или задающей величиной также может выступать один из следующих элементов:

bean | ref | idref | list | set | map | props | value | null

Объединение коллекций

Контейнер Spring также поддерживает объединение коллекций. Разработчик приложения может определить родительский элемент <list/>, <map/>, <set/> или <props/> и сделать так, чтобы дочерние элементы <list/>, <map/>, <set/> или <props/> наследовали и переопределяли значения из родительской коллекции. То есть значения дочерней коллекции являются результатом объединения элементов родительской и дочерней коллекций, при этом элементы дочерней коллекции переопределяют значения, указанные в родительской коллекции.

В этом разделе, посвященном объединению, рассмотрен механизм бинов "родитель-потомок". Читатели, не знакомые с определениями родительских (parent) и дочерних (child) бинов, возможно, захотят ознакомиться с соответствующим разделом, прежде чем продолжить.

В следующем примере показано объединение коллекций:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- объединение задается в определении дочерней коллекции -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

Обратите внимание на использование атрибута merge=true в элементе <props/> свойства adminEmails определения child бина. Если child бин разрешается и создается контейнером, то полученный экземпляр имеет коллекцию adminEmails Properties, которая содержит результат объединения коллекции adminEmails дочернего бина с коллекцией adminEmails родительского бина. В следующем листинге показан результат:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

Набор значений дочерней коллекции Properties наследует все элементы свойств от родительской <props/>, а значение дочерней коллекции для значения support переопределяет значение в родительской коллекции.

Такое поведение объединения применяется аналогично типам коллекций <list/>, <map/> и <set/>. В конкретном варианте элемента <list/> сохраняется семантика, связанная с типом коллекции List (то есть понятие ordered коллекции значений). Значения родительского списка предшествуют всем значениям дочернего списка. В случае типов коллекций Map, Set и Properties упорядочивание отсутствует. Следовательно, семантика упорядочивания не действует для типов коллекций, лежащих в основе связанных с ними типов реализации Map, Set и Properties, которые контейнер использует внутри.

Ограничения при объединении коллекций

Нельзя объединять коллекции разных типов (например, Map и List). Если попытаться это сделать, будет сгенерировано соответствующее Exception. Атрибут merge должен быть указан в нижнем, наследуемом, дочернем определении. Указание атрибута merge в определении родительской коллекции является избыточным и не приводит к желаемому объединению.

Строго типизированная коллекция

Благодаря поддержке в Java обобщенных типов, можно использовать строго типизированные коллекции. То есть можно объявить тип Collection таким образом, что он может содержать только (например) элементы String. Если вы используете Spring для внедрения строго типизированной Collection с зависимостями в бин, то можно воспользоваться поддержкой Spring по преобразованию типов, чтобы элементы экземпляров строго типизированной Collection были преобразованы в соответствующий типу перед добавлением в Collection. Нижеследующе определение класса Java и бина демонстрируют, как это сделать

Java
public class SomeClass {
    private Map<String, Float> accounts;
    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
Kotlin
class SomeClass {
    lateinit var accounts: Map<String, Float>
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

Если свойство accounts бина something готовится к внедрению, информация об обобщениях типа элемента строго типизированного Map<String, Float> становится доступной для отражения. Таким образом, инфраструктура преобразования типов Spring распознает различные элементы значений как имеющие тип Float, а строковые значения(9.99, 2.75 и 3.99) преобразуются в фактический тип Float.

Неопределенные и пустые строковые значения

Spring считает пустые аргументы для свойств и т.п. пустыми Strings. Следующий фрагмент конфигурационных метаданных на основе XML устанавливает для свойства email пустое String значение ("").

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

Предыдущая конфигурация эквивалентна следующему коду Java:

Java
exampleBean.setEmail("");
Kotlin
exampleBean.email = ""

Элемент <null/> обрабатывает null значения. В следующем листинге приведен пример:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

Предыдущая конфигурация эквивалентна следующему коду Java:

Java
exampleBean.setEmail(null);
Kotlin
exampleBean.email = null

Сокращение XML с помощью пространства имен p-namespace

Пространство имен p-namespace позволяет использовать атрибуты элемента bean (вместо вложенных элементов <property/>) для описания значений свойств взаимодействующих бинов или и того, и другого.

Spring поддерживает расширяемые форматы конфигурации с пространствами имен, которые основаны на определении схемы из XML Schema. Формат конфигурации beans, рассматриваемый в этой главе, определен в документе XML Schema. Однако пространство имен p-namespace не определено в XSD-файле и существует только в ядре Spring.

В следующем примере показаны два фрагмента XML (первый использует стандартный формат XML, а второй - пространство имен p-namespace), которые дают один и тот же результат:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>
    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

В примере показан атрибут в пространстве имен p-namespace под названием email в определении бина. Это указывает Spring на необходимость включить объявление свойства. Как уже говорилось ранее, пространство имен p-namespace не имеет определения схемы, поэтому можно задать в качестве имени атрибута имя свойства.

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

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>
    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>
    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

Этот пример включает в себя не только значение свойства с использованием пространства имен p-namespace, но и использует специальный формат для объявления ссылок на свойства. Если первое определение бина использует <property name="spouse" ref="jane"/> для создания ссылки от бина john к бину jane, то второе определение бина использует p:spouse-ref="jane" в качестве атрибута, чтобы осуществить то же самое. В данном случае spouse - это имя свойства, а часть -ref указывает на то, что это не прямое значение, а скорее ссылка на другой бин.

Пространство имен p-namespace не столь гибкое, как стандартный формат XML. Например, формат для объявления ссылок на свойства конфликтует со свойствами, которые заканчиваются на Ref, в то время как стандартный формат XML - нет. Рекомендуется тщательно выбирать подход и сообщать об этом членам вашей команды, чтобы избежать создания XML-документов, в которых используются все три подхода одновременно.

Сокращение XML с помощью пространства имен c-namespace

Подобно "Сокращению XML с помощью пространства имен p-namespace", пространство имен c-namespace, представленное в Spring 3.1, позволяет использовать встроенные атрибуты для настройки аргументов конструктора, а не вложенные элементы constructor-arg.

В следующем примере пространство имен с: используется для тех же действий, что и в примере из Внедрения зависимостей на основе конструктора:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
    <!-- традиционное объявление с необязательными именами аргументов -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>
    <!-- объявление пространства имен c-namespace именами аргументов -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>

Пространство имен c: использует те же соглашения (стандарты оформления), что и пространство имен p: (завершающий -ref для ссылок на бины) для задания аргументов конструктора по их именам. Точно так же его необходимо объявить в XML-файле, даже если он не определен в XSD-схеме (он существует внутри ядра Spring).

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

<!-- объявление индекса пространства имен c-namespace -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>
Ввиду грамматики XML в индексной записи (нотации) необходимо наличие ведущего символа _, поскольку имена атрибутов XML не могут начинаться с цифры (хотя некоторые IDE позволяют это делать). Соответствующая индексная запись также доступна для элементов <constructor-arg>, но не часто используется, поскольку в этих случаях обычно достаточно простого порядка объявления.

На практике механизм разрешения конструкторов достаточно эффективен при сопоставлении аргументов, поэтому, если только вам это действительно не нужно, рекомендуется использовать запись имени (name) во всей конфигурации

Составные имена свойств

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

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

Бин something имеет свойство fred, у которого есть свойство bob, у которого есть свойство sammy, и этому последнему свойству sammy присваивается значение 123. Чтобы это сработало, свойство fred бина something и свойство bob свойства fred не должны быть null после создания бина. В противном случае генерируется исключение NullPointerException.