Пакет org.springframework.beans соблюдает стандарт класса JavaBeans. JavaBean – это класс с конструктором по умолчанию без аргументов, который придерживается соглашения об именовании, где (например) свойство с именем bingoMadness будет иметь сеттер setBingoMadness(…) и геттер getBingoMadness(). Для получения дополнительной информации о JavaBeans и спецификации см. javabeans.

Одним из важных классов в пакете бинов является интерфейс BeanWrapper и его соответствующая реализация(BeanWrapperImpl). Как указано в javadoc, BeanWrapper предлагает функциональность для установки и получения значений свойств (по отдельности или массово), получения дескрипторов свойств и запроса свойств для того, чтобы определять, доступны ли они для чтения или записи. Кроме того, BeanWrapper предлагает поддержку вложенных свойств, что позволяет устанавливать свойства для подсвойств на неограниченную глубину. BeanWrapper также поддерживает возможность добавления стандартных JavaBeans PropertyChangeListeners и VetoableChangeListeners без нужды во вспомогательном коде в целевом классе. И последнее, но не менее важное: BeanWrapper обеспечивает поддержку установки индексированных свойств. BeanWrapper обычно не используется кодом приложения напрямую, но используется DataBinder и BeanFactory.

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

Задание и получение базовых и вложенных свойств

Установка и получение свойств осуществляется с помощью перегруженных методов setPropertyValue и getPropertyValue в BeanWrapper. Подробнее см. в их Javadoc. В приведенной ниже таблице показаны некоторые примеры этих соглашений:

Таблица 11. Примеры свойств
Выражение Пояснение

name

Указывает name свойства, которое соответствует методам getName() или isName() и setName(..).

account.name

Указывает name вложенного свойства account, которое соответствует (например) методам getAccount().setName() или getAccount().getName().

account[2]

Указывает третий элемент индексированного свойства account имущества. Индексированные свойства могут быть типа array, list или другой естественно упорядоченной коллекции.

account[COMPANYNAME]

Указывает значение записи ассоциативного массива, проиндексированной по ключу COMPANYNAME свойства Account Map.

(Этот следующий раздел не является чрезвычайно важным, если вы не планируете работать с BeanWrapper напрямую. Если вы используете только DataBinder и BeanFactory и их реализации по умолчанию, то следует сразу перейти к PropertyEditors.)

Следующие два примера классов используют BeanWrapper для получения и установки свойств:

Java
public class Company {
    private String name;
    private Employee managingDirector;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Employee getManagingDirector() {
        return this.managingDirector;
    }
    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
Kotlin
class Company {
    var name: String? = null
    var managingDirector: Employee? = null
}
Java
public class Employee {
    private String name;
    private float salary;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public float getSalary() {
        return salary;
    }
    public void setSalary(float salary) {
        this.salary = salary;
    }
}
Kotlin
class Employee {
    var name: String? = null
    var salary: Float? = null
}

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

Java
BeanWrapper company = new BeanWrapperImpl(new Company());
// установка названия компании...
company.setPropertyValue("name", "Some Company Inc.");
// ... также можно выполнить следующим образом:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// Ладно, давайте создадим директора и привяжем его к компании:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// получение salary для managingDirector через company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
Kotlin
val company = BeanWrapperImpl(Company())
// установка названия компании...
company.setPropertyValue("name", "Some Company Inc.")
// ... также можно выполнить следующим образом:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// Ладно, давайте создадим директора и привяжем его к компании:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// получение salary для managingDirector через company
val salary = company.getPropertyValue("managingDirector.salary") as Float?

Встроенные реализации PropertyEditor

Spring использует концепцию PropertyEditor для выполнения преобразования между Object и String. Может быть удобно представлять свойства иначе, чем сам объект. Например, Date можно представить в удобочитаемом для человека виде (как String: '2007-14-09'), в то время как мы все еще можем преобразовать человекочитаемую форму обратно в исходную дату (или, что еще лучше, преобразовать любую дату, введенную в человекочитаемой форме, обратно в объекты Date). Такой логики работы можно добиться, зарегистрировав специальные редакторы типа java.beans.PropertyEditor. Регистрация специальных редакторов для BeanWrapper или, как вариант, в определенном IoC-контейнере (как упоминалось в предыдущей главе), дает ему сведения о том, как преобразовать свойства в нужный тип. Для получения дополнительной информации о PropertyEditor, см. javadoc по пакету java.beans от Oracle.

Несколько примеров использования функции редактирования свойств в Spring:

  • Установка свойств бинов осуществляется с помощью реализаций PropertyEditor. Если вы используете String в качестве значения свойства какого-либо бина, который вы объявляете в XML-файле, Spring (если сеттер соответствующего свойства имеет параметр Class) использует ClassEditor, чтобы попытаться разрешить параметр в объект Class.

  • Синтаксический анализ параметров HTTP-запроса в MVC-фреймворке Spring осуществляется с помощью всевозможных реализаций PropertyEditor, которые можно вручную привязать во всех подклассах CommandController.

Spring имеет ряд встроенных реализаций PropertyEditor, которые облегчают работу. Все они находятся в пакете org.springframework.beans.propertyeditors. Большинство из них (но не все, как указано в следующей таблице) по умолчанию регистрируются BeanWrapperImpl. В случаях, если редактор свойств настраивается каким-либо определенным образом, вы можете зарегистрировать свой собственный вариант, чтобы переопределить вариант по умолчанию. В следующей таблице описаны различные реализации PropertyEditor, которые предоставляет Spring:

Таблица 12. Встроенные реализации PropertyEditor
Класс Пояснение

ByteArrayPropertyEditor

Редактор для байтовых массивов. Преобразует строки в соответствующие им байтовые представления. По умолчанию регистрируется BeanWrapperImpl.

ClassEditor

Осуществляет синтаксический разбор строк, представляющих классы, на реальные классы и наоборот. Если класс не найден, генерируется исключение IllegalArgumentException. По умолчанию регистрируется BeanWrapperImpl.

CustomBooleanEditor

Настраиваемый редактор свойств для Boolean свойств. По умолчанию регистрируется BeanWrapperImpl, но может быть переопределен путем регистрации его пользовательского экземпляра в качестве специализированного редактора.

CustomCollectionEditor

Редактор свойств для коллекций, преобразующий любую исходную Collection в заданный тип целевой Collection.

CustomDateEditor

Настраиваемый редактор свойств для java.util.Date, поддерживающий специальный DateFormat. НЕ регистрируется по умолчанию. Пользователь должен регистрировать его в соответствующем формате по мере необходимости.

CustomNumberEditor

Настраиваемый редактор свойств для любого подкласса Number, такого как Integer, Long, Float или Double. По умолчанию регистрируется BeanWrapperImpl, но может быть переопределен путем регистрации его пользовательского экземпляра в качестве специализированного редактора.

FileEditor

Разрешает строки в объекты java.io.File. По умолчанию регистрируется BeanWrapperImpl.

InputStreamEditor

Однонаправленный редактор свойств, который может принимать строку и создавать (через промежуточный ResourceEditor и Resource) InputStream, чтобы свойства InputStream могли быть непосредственно установлены как строки. Обратите внимание, что при использовании по умолчанию InputStream не закрывается. По умолчанию регистрируется BeanWrapperImpl.

LocaleEditor

Может преобразовывать строки в объекты Locale и наоборот (формат строки - [language]_[country]_[variant], такой же, как метод toString() в Locale). Также принимает пробелы в качестве разделителей, как альтернативу подчеркиванию. По умолчанию регистрируется BeanWrapperImpl.

PatternEditor

Может разрешать строки в объекты java.util.regex.Pattern и наоборот.

PropertiesEditor

Может преобразовывать строки (отформатированные в формате, определенном в javadoc класса java.util.Properties) в объекты Properties. По умолчанию регистрируется BeanWrapperImpl.

StringTrimmerEditor

Редактор свойств, который обрезает строки. Опционально позволяет трансформировать пустую строку в значение null. По умолчанию НЕ зарегистрирован - должен быть зарегистрирован пользователем.

URLEditor

Может преобразовать строковое представление какого-либо URL-адреса в реальный объект URL. По умолчанию регистрируется BeanWrapperImpl.

Spring использует java.beans.PropertyEditorManager для установки пути поиска для редакторов свойств, которые могут понадобиться. Путь поиска также включает sun.bean.editors, который включает реализации PropertyEditor для таких типов, как Font, Color и большинства примитивных типов. Обратите внимание, что стандартная инфраструктура класса JavaBeans автоматически обнаруживает классы PropertyEditor (без необходимости их явной регистрации), если они находятся в том же пакете, что и обрабатываемый ими класс, и имеют то же имя, что и этот класс, с добавлением Editor. Например, можно иметь следующую структуру классов и пакетов, которой будет достаточно, чтобы класс SomethingEditor распознавался и использовался как PropertyEditor для свойств типа Something.

com
  chank
    pop
      Something
      SomethingEditor // PropertyEditor для класса Something

Обратите внимание, что в этом случае также можно использовать стандартный механизм BeanInfo класса JavaBeans (в некоторой степени описанный здесь). В следующем примере используется механизм BeanInfo для явной регистрации одного или нескольких экземпляров PropertyEditor со свойствами ассоциированного класса:

com
  chank
    pop
      Something
      SomethingBeanInfo // BeanInfo для класса Something

Следующий исходный код Java для ссылающегося класса SomethingBeanInfo связывает CustomNumberEditor со свойством age класса Something:

Java
public class SomethingBeanInfo extends SimpleBeanInfo {
    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                @Override
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                }
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
Kotlin
class SomethingBeanInfo : SimpleBeanInfo() {
    override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
        try {
            val numberPE = CustomNumberEditor(Int::class.java, true)
            val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
                override fun createPropertyEditor(bean: Any): PropertyEditor {
                    return numberPE
                }
            }
            return arrayOf(ageDescriptor)
        } catch (ex: IntrospectionException) {
            throw Error(ex.toString())
        }
    }
}

Регистрация дополнительных реализаций специального PropertyEditor

При задании свойств бина как строковых значений, IoC-контейнер Spring в конечном итоге использует стандартные реализации JavaBeans PropertyEditor для преобразования этих строк в сложный тип свойства. Spring предварительно регистрирует ряд кастомных реализаций PropertyEditor (например, для преобразования имени класса, выраженного в виде строки, в объект Class). Кроме того, стандартный механизм поиска PropertyEditor в JavaBeans позволяет назвать PropertyEditor для класса соответствующим образом и поместить его в тот же пакет, что и класс, для которого он обеспечивает поддержку, чтобы его можно было найти автоматически.

В случае возникновения необходимости зарегистрировать другие кастомные PropertyEditors доступно несколько механизмов. Наиболее ручной подход, который обычно не считается удобным или рекомендуемым, заключается в использовании метода registerCustomEditor() интерфейса ConfigurableBeanFactory, при условии, что имеется ссылка на BeanFactory. Другой (немного более удобный) механизм – это использование специализированного постпроцессора фабрики бинов под названием CustomEditorConfigurer. Хотя и нельзя использовать постпроцессоры фабрики бинов при помощи реализации BeanFactory, CustomEditorConfigurer имеет вложенную настройку свойств, поэтому мы настоятельно рекомендуем использовать его с ApplicationContext в случаях, в которых его можно развернуть аналогично любому другому бину и в которых он может быть автоматически обнаружен и применен.

Обратите внимание, что все фабрики бинов и контексты приложений автоматически используют ряд встроенных редакторов свойств благодаря задействованию BeanWrapper для обработки преобразований свойств. Кроме того, ApplicationContextтакже переопределяет или добавляет дополнительные редакторы для обработки поиска ресурсов в соответствии с конкретным типом контекста приложения.

Стандартные экземпляры PropertyEditor класса JavaBeans используются для преобразования значений свойств, выраженных в виде строк, в фактический сложный тип свойства. Можно использовать CustomEditorConfigurer, постпроцессор фабрики бинов, для удобного добавления поддержки дополнительных экземпляров PropertyEditor в ApplicationContext.

Рассмотрим следующий пример, в котором определен пользовательский класс ExoticType и другой класс DependsOnExoticType, которому в качестве свойства требуется установить ExoticType:

Java
package example;
public class ExoticType {
    private String name;
    public ExoticType(String name) {
        this.name = name;
    }
}
public class DependsOnExoticType {
    private ExoticType type;
    public void setType(ExoticType type) {
        this.type = type;
    }
}
Kotlin
package example
class ExoticType(val name: String)
class DependsOnExoticType {
    var type: ExoticType? = null
}

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

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

Реализация PropertyEditor может выглядеть следующим образом:

Java
// преобразует строковое представление в объект ExoticType
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}
Kotlin
// преобразует строковое представление в объект ExoticType
package example
import java.beans.PropertyEditorSupport
class ExoticTypeEditor : PropertyEditorSupport() {
    override fun setAsText(text: String) {
        value = ExoticType(text.toUpperCase())
    }
}

Наконец, в следующем примере показано, как использовать CustomEditorConfigurer для регистрации нового PropertyEditor в ApplicationContext, который затем сможет использовать его по мере необходимости:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
Использование PropertyEditorRegistrar

Другим механизмом регистрации редакторов свойств в контейнере Spring является создание и использование PropertyEditorRegistrar. Этот интерфейс особенно полезен, если нужно использовать один и тот же набор редакторов свойств в нескольких различных ситуациях. Можно написать соответствующий регистратор (registrar) и использовать его повторно в каждом случае. Экземпляры PropertyEditorRegistrar работают совместно с интерфейсом PropertyEditorRegistry, который реализуется BeanWrapper из Spring (и DataBinder). Экземпляры PropertyEditorRegistrar особенно удобны при использовании в сочетании с CustomEditorConfigurer, который предоставляет свойство setPropertyEditorRegistrars(..). Экземпляры PropertyEditorRegistrar, добавленные в CustomEditorConfigurer таким образом, можно легко использовать вместе с контроллерами DataBinder и Spring MVC. Кроме того, это позволяет избежать необходимости синхронизации в кастомных редакторах: Ожидается, что PropertyEditorRegistrar будет создавать новые экземпляры PropertyEditor при каждой попытке создания бина.

В следующем примере показано, как создать собственную реализацию PropertyEditorRegistrar:

Java
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        // ожидается, что будут созданы новые экземпляры PropertyEditor
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
        // в этом случае можно зарегистрировать столько кастомных редакторов свойств, сколько требуется...
    }
}
Kotlin
package com.foo.editors.spring
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {
    override fun registerCustomEditors(registry: PropertyEditorRegistry) {
        // ожидается, что будут созданы новые экземпляры PropertyEditor
        registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())
        // в этом случае можно зарегистрировать столько специальных редакторов свойств, сколько требуется...
    }
}

Смотрите также пример реализации PropertyEditorRegistrar в org.springframework.beans.support.ResourceEditorRegistrar. Обратите внимание, как в своей реализации метода registerCustomEditors(…) он создает новые экземпляры каждого редактора свойств.

Следующий пример показывает, как настроить CustomEditorConfigurer и внедрить в него экземпляр нашего CustomPropertyEditorRegistrar:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>
<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

Наконец (и немного отклоняясь от темы этой главы) для тех из вас, кто использует веб-фреймворк MVC из Spring, использовать PropertyEditorRegistrar в сочетании с контроллерами сбора, обработки и интерпретации данных в приложении интернета, привязывающими данные, может быть очень удобно. В следующем примере используется PropertyEditorRegistrar в реализации метода @InitBinder:

Java
@Controller
public class RegisterUserController {
    private final PropertyEditorRegistrar customPropertyEditorRegistrar;
    RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }
    @InitBinder
    void initBinder(WebDataBinder binder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }
    // другие методы, связанные с регистрацией пользователя
}
Kotlin
@Controller
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {
    @InitBinder
    fun initBinder(binder: WebDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }
    // другие методы, связанные с регистрацией пользователя
}

Такой стиль регистрации PropertyEditor может сделать код лаконичным (реализация метода @InitBinder занимает всего одну строку) и позволяет инкапсулировать общий код регистрации PropertyEditor в класс, а затем позволить совместно его использовать многим контроллерам.