Пакет 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. В приведенной ниже таблице показаны некоторые примеры этих соглашений:
Выражение | Пояснение |
---|---|
|
Указывает |
|
Указывает |
|
Указывает третий элемент индексированного свойства |
|
Указывает значение записи ассоциативного массива, проиндексированной по ключу |
(Этот следующий раздел не является чрезвычайно важным, если вы не планируете работать с BeanWrapper
напрямую. Если вы используете только DataBinder
и BeanFactory
и их реализации по умолчанию, то следует сразу перейти к PropertyEditors
.)
Следующие два примера классов используют BeanWrapper
для получения и установки свойств:
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;
}
}
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
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;
}
}
class Employee {
var name: String? = null
var salary: Float? = null
}
Следующие фрагменты кода демонстрируют примеры получения и манипулирования некоторыми свойствами созданных экземпляров Company
и Employees
:
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");
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:
Класс | Пояснение |
---|---|
|
Редактор для байтовых массивов. Преобразует строки в соответствующие им байтовые представления. По умолчанию регистрируется |
|
Осуществляет синтаксический разбор строк, представляющих классы, на реальные классы и наоборот. Если класс не найден, генерируется исключение |
|
Настраиваемый редактор свойств для |
|
Редактор свойств для коллекций, преобразующий любую исходную |
|
Настраиваемый редактор свойств для |
|
Настраиваемый редактор свойств для любого подкласса |
|
Разрешает строки в объекты |
|
Однонаправленный редактор свойств, который может принимать строку и создавать (через промежуточный |
|
Может преобразовывать строки в объекты |
|
Может разрешать строки в объекты |
|
Может преобразовывать строки (отформатированные в формате, определенном в javadoc класса |
|
Редактор свойств, который обрезает строки. Опционально позволяет трансформировать пустую строку в значение |
|
Может преобразовать строковое представление какого-либо URL-адреса в реальный объект |
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
:
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());
}
}
}
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
:
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;
}
}
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
может выглядеть следующим образом:
// преобразует строковое представление в объект ExoticType
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
// преобразует строковое представление в объект 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
:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// ожидается, что будут созданы новые экземпляры PropertyEditor
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// в этом случае можно зарегистрировать столько кастомных редакторов свойств, сколько требуется...
}
}
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
:
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// другие методы, связанные с регистрацией пользователя
}
@Controller
class RegisterUserController(
private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {
@InitBinder
fun initBinder(binder: WebDataBinder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder)
}
// другие методы, связанные с регистрацией пользователя
}
Такой стиль регистрации PropertyEditor
может сделать код лаконичным (реализация метода @InitBinder
занимает всего одну строку) и позволяет инкапсулировать общий код регистрации PropertyEditor
в класс, а затем позволить совместно его использовать многим контроллерам.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ