Пакет 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.");
// ... cтакож можна виконати так:
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 for class Something

Зверни увагу, що в цьому випадку також можна використовувати стандартний механізм BeanInfo класу JavaBeans (деякою мірою описаний тут). У наступному прикладі використовується механізм BeanInfo для явної реєстрації одного або декількох екземплярів PropertyEditor з властивостями асоційованого класу:

com
  chank
    pop
      Something
      SomethingBeanInfo // BeanInfo for class 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 {
        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 у випадках, в яких його можна розгорнути аналогічно будь-якому іншому біну і в яких він може бути автоматично виявлений і застосований для обробки перетворень властивостей. 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()));
    }
}v        
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. Цей інтерфейс особливо корисний, якщо потрібно використовувати один і той самий набір редакторів властивостей у різних ситуаціях. Можна написати відповідний реєстратор і використовувати його повторно в кожному випадку. Примірники 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.PropertyEditorRegistrar
class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {
    override fun registerCustomEditors(registry: PropertyEditorRegistry) {
        // очікується, що будуть створені нові екземпляри PropertyEditor
        register.CustomEditor(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 до класу, а потім дозволити спільно використовувати його багатьом контролерам.