Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
Carlos Martins Getting Started with Juffrou-reflectJuffrou-reflect is focused on reflection, and offers a very performant bean wrapper allowing bean introspection and manipulation through property names.
To start using Juffrou-XML in your maven project just add the following dependency:
<dependency> <groupId>net.sf.juffrou</groupId> <artifactId>juffrou-reflect</artifactId> <version></version> </dependency>
Download the file juffrou--bundle.zip from the website and extract it's contents to a temporary directory.
Add juffrou-reflect-.jar to the classpath of your project and you are good to go.
JuffrouBeanWrapper is the object that wraps around your beans and allows you to inspect them through the names of their properties. You can access the wrapped bean's properties and also the properties of beans referenced by them.
Example class diagramLink shows one class Person with three simple attributes and one attribute of type Address. The Address class is also shown and has two simple attributes.
Person and Address will be used extensively throughout this manual in code examples. A tipical use of JuffrouBeanWrapper could be:JuffrouBeanWrapper bw = new JuffrouBeanWrapper(Person.class); bw.setValue("firstName", "Carlos"); bw.setValue("home.city", "Lisboa"); Person person = (Person) bw.getBean();
Instantiate JuffrouBeanWrapper around a Person class
Set the firstName property of Person with the value "Carlos".
Set the home property of Person with a new instance of an Address class and set the city property of Address to the value "Lisboa".
Get the instance of the wrapped object.
When the program executes bw.setValue("firstName", "Carlos"); the bean wrapper creates an instance of Person. And when it executes bw.setValue("home.city", "Lisboa"); it will instantiate an Address class, set the value of the property city in Address to "Lisboa" and set the value of the property home in Person to the created Address instance.
When you execute the method bw.setValue("birthDay", someObject) to set the value of the birthDay property, the JuffrouBeanWrapper expects someObject to be of the same type as the bean property (in this case Date). And if it is not, it will throw an IllegalArgumentException exception.
But if you use the method setValueOfString("birthDay", "1967-10-01"), then the JuffrouBeanWrapper will try to convert the string value into the type of the property before setting it.
The methods addElement(String propertyName, Object element) and removeElement(String propertyName, Object element) allow you to add and remove elements from a bean property of type collection.
These methods will add/remove the element directly from the collection property or call a method in the underlying bean to do the job.
Example bean with a collection propertyIf you have a bean with a property like List<Address> addresses and that bean contains the methods addAddress(Address address) and removeAddress(Address address), then these methods will be used to add remove elements from the collection. If not, then the normall collection methods will be used. In this case, the method addElement(propertyName, element) will be the equivalent of ((Collection)getValue(propertyName)).add(element).
When you access a nested property (i.e. a property of a nested bean), like in the case of bw.setValue("home.city", "Lisboa"), the JuffrouBeanWrapper automatically creates another JuffrouBeanWrapper around the nested bean. In this case around an Address bean. This is called a nested bean wrapper.
Nested JuffrouBeanWrappers are created only when referenced by beanWrapper.getValue, beanWrapper.setValue, beanWrapper.getType, beanWrapper.getClass or beanWrapper.getNestedWrapper.
You can get a specific nested bean wrapper with the method beanWrapper.getNestedWrapper("home") and you can get all current nested bean wrappers with the method beanWrapper.getNestedWrappers().
If you want to change the wrapped instance without creating a new JuffrouBeanWrapper you can call bw.setBean(newInstance) and if you want to zero all properties of the wrapped instance you can call bw.setBean(null).
In case you don't know the details of the Wrapped object, you can inquire the JuffrouBeanWrapper:
BeanWrapperContext context = BeanWrapperContext.create(Programmer.class); JuffrouBeanWrapper beanWrapper = new JuffrouBeanWrapper(context); for(String propertyName : beanWrapper.getPropertyNames()) { Type type = beanWrapper.getType(propertyName); Object value = beanWrapper.getValue(propertyName); System.out.println(type + ": " + value); }
Sometimes you want to have control over bean instantiation and would like to set some "preferences" over how JuffrouBeanWrapper behaves. This is where the BeanWrapperContext comes in.
The BeanWrapperContext is the object that holds metadata for a BeanWrapper. This metadata is composed by data collected through class introspection, including the references to the getter and setter methods of the class and all classes it extends.
If you instantiate a BeanWrapper using the default constructor, it will create a new BeanWrapperContext. But if you instantiate a BeanWrapper by passing a BeanWrapperContext, no introspection overhead is needed.
Mode | Instantiation only | With property setting |
---|---|---|
Spring Framework's BeanWrapperImpl | 64 | 291 |
JuffrouBeanWrapper | 9 | 41 |
JuffrouBeanWrapper with BeanWrapperContext | 1 | 20 |
JuffrouBeanWrapper with BeanWrapperFactory | 9 | 20 |
When a JuffrouBeanWrapper creates a nested JuffrouBeanWrapper it also creates a nested BeanWrapperContext, of course. But it will only create one BeanWrapperContext per property type, so, for instance, if you have a BeanWrapper around a class Person with two properties (home and work) of type Address, you can have two nested JuffrouBeanWrappers (one for home and another for work), but only one nested BeanWrapperContext.
You can obtain the BeanWrapperContext of a JuffrouBeanWrapper at any time calling the method beanWrapper.getContext().
With BeanWrapperContext you can define a class that will be called to instantiate the wrapped class as well as the classes of the nested beans whenever they need to be instantiated. To do this, first create a class that implements the interface BeanInstanceBuilder. See the following example:
BeanInstanceBuilder iCreator = new BeanInstanceBuilder() { @Override public Object build(Class clazz) throws BeanInstanceBuilderException { Programmer programmer = new Programmer(); programmer.setLastName("Smith"); return programmer; } }; BeanWrapperContext context = BeanWrapperContext.create(Programmer.class); context.setBeanInstanceBuilder(iCreator); JuffrouBeanWrapper bw = new JuffrouBeanWrapper(context); bw.setValue("firstName", "John"); Programmer programmer = (Programmer) bw.getBean(); Assert.assertEquals("John", programmer.getFirstName()); Assert.assertEquals("Smith", programmer.getLastName());
You might also want to associate more information with a bean than the introspection information collected by the BeanWrapperContext. For instance you might want add information to help represent the bean in XML format (like Juffrou-XML does). This is where the CustomizableBeanWrapperFactory comes in.
The BeanWrapperFactory is the object responsible for creating all the BeanWrapperContexts. It does mainly three things:
In fact you can also use the BeanWrapperFactory to instantiate JuffrouBeanWrappers using the methods factory.getBeanWrapper(Person.class) or factory.getBeanWrapper(personIntance) for example. This is as fast as instantiating a JuffrouBeanWrapper with a BeanWrapperContext parameter.
So if you want to extend the BeanWrapperContext and add to the bean metadata all you have to do is create a class that extends BeanWrapperContext and "tell" the CustomizableBeanWrapperFactory to instantiate your class instead of BeanWrapperContext.
public class MyBeanWrapperContext extends BeanWrapperContext { //TODO create properties to extend the context public MyBeanWrapperContext(CustomizableBeanWrapperFactory factory, Class clazz, Type... types) { super(factory, clazz, types); //TODO some initialization } }
And how do we "tell" the CustomizableBeanWrapperFactory to use this as BeanWrapperContext? Easy. We create a class that implements BeanContextBuilder like this one:
public class MyContextBuilder implements BeanContextBuilder { @Override public MyBeanWrapperContext build( CustomizableBeanWrapperFactory factory, Class clazz, Type... types) { MyBeanWrapperContext context = new MyBeanWrapperContext(factory, clazz, types); return context; } }
And then we "tell" CustomizableBeanWrapperFactory to use this builder. See the example bellow:
CustomizableBeanWrapperFactory factory = new CustomizableBeanWrapperFactory(); factory.setBeanContextBuilder(new MyContextBuilder()); JuffrouBeanWrapper myPersonWrapper = factory.getBeanWrapper(Person.class); MyBeanWrapperContext context = (MyBeanWrapperContext) myPersonWrapper.getContext();
The BeanConverter is a utility class to convert between two beans.
Given any two beans and a map that establishes which properties in bean 1 correspond to properties in bean 2, this class can be used to automatically obtain bean 1 from an instance of bean 2 and vice-versa.
The ReflectionUtil is a utility class with several helper static methods. They are all well documented in Javadoc for an easy and direct reference.
Some of these methods are: