For the latest stable version, please use Spring Framework 6.2.0! |
Bean Manipulation and the BeanWrapper
The org.springframework.beans
package adheres to the JavaBeans standard.
A JavaBean is a class with a default no-argument constructor and that follows
a naming convention where (for example) a property named bingoMadness
would
have a setter method setBingoMadness(..)
and a getter method getBingoMadness()
. For
more information about JavaBeans and the specification, see
javabeans.
One quite important class in the beans package is the BeanWrapper
interface and its
corresponding implementation (BeanWrapperImpl
). As quoted from the javadoc, the
BeanWrapper
offers functionality to set and get property values (individually or in
bulk), get property descriptors, and query properties to determine if they are
readable or writable. Also, the BeanWrapper
offers support for nested properties,
enabling the setting of properties on sub-properties to an unlimited depth. The
BeanWrapper
also supports the ability to add standard JavaBeans PropertyChangeListeners
and VetoableChangeListeners
, without the need for supporting code in the target class.
Last but not least, the BeanWrapper
provides support for setting indexed properties.
The BeanWrapper
usually is not used by application code directly but is used by the
DataBinder
and the BeanFactory
.
The way the BeanWrapper
works is partly indicated by its name: it wraps a bean to
perform actions on that bean, such as setting and retrieving properties.
Setting and Getting Basic and Nested Properties
Setting and getting properties is done through the setPropertyValue
and
getPropertyValue
overloaded method variants of BeanWrapper
. See their Javadoc for
details. The below table shows some examples of these conventions:
Expression | Explanation |
---|---|
|
Indicates the property |
|
Indicates the nested property |
|
Indicates the third element of the indexed property |
|
Indicates the value of the map entry indexed by the |
(This next section is not vitally important to you if you do not plan to work with
the BeanWrapper
directly. If you use only the DataBinder
and the BeanFactory
and their default implementations, you should skip ahead to the
section on PropertyEditors
.)
The following two example classes use the BeanWrapper
to get and set
properties:
-
Java
-
Kotlin
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
}
-
Java
-
Kotlin
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
}
The following code snippets show some examples of how to retrieve and manipulate some of
the properties of instantiated Company
s and Employee
s:
-
Java
-
Kotlin
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
Built-in PropertyEditor
Implementations
Spring uses the concept of a PropertyEditor
to effect the conversion between an
Object
and a String
. It can be handy
to represent properties in a different way than the object itself. For example, a Date
can be represented in a human readable way (as the String
: '2007-14-09'
), while
we can still convert the human readable form back to the original date (or, even
better, convert any date entered in a human readable form back to Date
objects). This
behavior can be achieved by registering custom editors of type
java.beans.PropertyEditor
. Registering custom editors on a BeanWrapper
or,
alternatively, in a specific IoC container (as mentioned in the previous chapter), gives it
the knowledge of how to convert properties to the desired type. For more about
PropertyEditor
, see the javadoc of the java.beans
package from Oracle.
A couple of examples where property editing is used in Spring:
-
Setting properties on beans is done by using
PropertyEditor
implementations. When you useString
as the value of a property of some bean that you declare in an XML file, Spring (if the setter of the corresponding property has aClass
parameter) usesClassEditor
to try to resolve the parameter to aClass
object. -
Parsing HTTP request parameters in Spring’s MVC framework is done by using all kinds of
PropertyEditor
implementations that you can manually bind in all subclasses of theCommandController
.
Spring has a number of built-in PropertyEditor
implementations to make life easy.
They are all located in the org.springframework.beans.propertyeditors
package. Most, (but not all, as indicated in the following table) are, by default, registered by
BeanWrapperImpl
. Where the property editor is configurable in some fashion, you can
still register your own variant to override the default one. The following table describes
the various PropertyEditor
implementations that Spring provides:
Class | Explanation |
---|---|
|
Editor for byte arrays. Converts strings to their corresponding byte
representations. Registered by default by |
|
Parses Strings that represent classes to actual classes and vice-versa. When a
class is not found, an |
|
Customizable property editor for |
|
Property editor for collections, converting any source |
|
Customizable property editor for |
|
Customizable property editor for any |
|
Resolves strings to |
|
One-way property editor that can take a string and produce (through an
intermediate |
|
Can resolve strings to |
|
Can resolve strings to |
|
Can convert strings (formatted with the format defined in the javadoc of the
|
|
Property editor that trims strings. Optionally allows transforming an empty string
into a |
|
Can resolve a string representation of a URL to an actual |
Spring uses the java.beans.PropertyEditorManager
to set the search path for property
editors that might be needed. The search path also includes sun.bean.editors
, which
includes PropertyEditor
implementations for types such as Font
, Color
, and most of
the primitive types. Note also that the standard JavaBeans infrastructure
automatically discovers PropertyEditor
classes (without you having to register them
explicitly) if they are in the same package as the class they handle and have the same
name as that class, with Editor
appended. For example, one could have the following
class and package structure, which would be sufficient for the SomethingEditor
class to be
recognized and used as the PropertyEditor
for Something
-typed properties.
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
Note that you can also use the standard BeanInfo
JavaBeans mechanism here as well
(described to some extent
here). The
following example uses the BeanInfo
mechanism to explicitly register one or more
PropertyEditor
instances with the properties of an associated class:
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
The following Java source code for the referenced SomethingBeanInfo
class
associates a CustomNumberEditor
with the age
property of the Something
class:
-
Java
-
Kotlin
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())
}
}
}
Registering Additional Custom PropertyEditor
Implementations
When setting bean properties as string values, a Spring IoC container ultimately uses
standard JavaBeans PropertyEditor
implementations to convert these strings to the complex type of the
property. Spring pre-registers a number of custom PropertyEditor
implementations (for example, to
convert a class name expressed as a string into a Class
object). Additionally,
Java’s standard JavaBeans PropertyEditor
lookup mechanism lets a PropertyEditor
for a class be named appropriately and placed in the same package as the class
for which it provides support, so that it can be found automatically.
If there is a need to register other custom PropertyEditors
, several mechanisms are
available. The most manual approach, which is not normally convenient or
recommended, is to use the registerCustomEditor()
method of the
ConfigurableBeanFactory
interface, assuming you have a BeanFactory
reference.
Another (slightly more convenient) mechanism is to use a special bean factory
post-processor called CustomEditorConfigurer
. Although you can use bean factory post-processors
with BeanFactory
implementations, the CustomEditorConfigurer
has a
nested property setup, so we strongly recommend that you use it with the
ApplicationContext
, where you can deploy it in similar fashion to any other bean and
where it can be automatically detected and applied.
Note that all bean factories and application contexts automatically use a number of
built-in property editors, through their use of a BeanWrapper
to
handle property conversions. The standard property editors that the BeanWrapper
registers are listed in the previous section.
Additionally, ApplicationContext
s also override or add additional editors to handle
resource lookups in a manner appropriate to the specific application context type.
Standard JavaBeans PropertyEditor
instances are used to convert property values
expressed as strings to the actual complex type of the property. You can use
CustomEditorConfigurer
, a bean factory post-processor, to conveniently add
support for additional PropertyEditor
instances to an ApplicationContext
.
Consider the following example, which defines a user class called ExoticType
and
another class called DependsOnExoticType
, which needs ExoticType
set as a property:
-
Java
-
Kotlin
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
}
When things are properly set up, we want to be able to assign the type property as a
string, which a PropertyEditor
converts into an actual
ExoticType
instance. The following bean definition shows how to set up this relationship:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
The PropertyEditor
implementation could look similar to the following:
-
Java
-
Kotlin
package example;
import java.beans.PropertyEditorSupport;
// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
package example
import java.beans.PropertyEditorSupport
// converts string representation to ExoticType object
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
value = ExoticType(text.toUpperCase())
}
}
Finally, the following example shows how to use CustomEditorConfigurer
to register the new PropertyEditor
with the
ApplicationContext
, which will then be able to use it as needed:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
Using PropertyEditorRegistrar
Another mechanism for registering property editors with the Spring container is to
create and use a PropertyEditorRegistrar
. This interface is particularly useful when
you need to use the same set of property editors in several different situations.
You can write a corresponding registrar and reuse it in each case.
PropertyEditorRegistrar
instances work in conjunction with an interface called
PropertyEditorRegistry
, an interface that is implemented by the Spring BeanWrapper
(and DataBinder
). PropertyEditorRegistrar
instances are particularly convenient
when used in conjunction with CustomEditorConfigurer
(described
here), which exposes a property
called setPropertyEditorRegistrars(..)
. PropertyEditorRegistrar
instances added
to a CustomEditorConfigurer
in this fashion can easily be shared with DataBinder
and
Spring MVC controllers. Furthermore, it avoids the need for synchronization on custom
editors: A PropertyEditorRegistrar
is expected to create fresh PropertyEditor
instances for each bean creation attempt.
The following example shows how to create your own PropertyEditorRegistrar
implementation:
-
Java
-
Kotlin
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
package com.foo.editors.spring
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {
override fun registerCustomEditors(registry: PropertyEditorRegistry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())
// you could register as many custom property editors as are required here...
}
}
See also the org.springframework.beans.support.ResourceEditorRegistrar
for an example
PropertyEditorRegistrar
implementation. Notice how in its implementation of the
registerCustomEditors(..)
method, it creates new instances of each property editor.
The next example shows how to configure a CustomEditorConfigurer
and inject an instance
of our CustomPropertyEditorRegistrar
into it:
<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"/>
Finally (and in a bit of a departure from the focus of this chapter) for those of you
using Spring’s MVC web framework, using a PropertyEditorRegistrar
in
conjunction with data-binding web controllers can be very convenient. The following
example uses a PropertyEditorRegistrar
in the implementation of an @InitBinder
method:
-
Java
-
Kotlin
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods related to registering a User
}
@Controller
class RegisterUserController(
private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {
@InitBinder
fun initBinder(binder: WebDataBinder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder)
}
// other methods related to registering a User
}
This style of PropertyEditor
registration can lead to concise code (the implementation
of the @InitBinder
method is only one line long) and lets common PropertyEditor
registration code be encapsulated in a class and then shared amongst as many controllers
as needed.