BeanUtil

What is a Bean?

A JavaBean is an object with setter and getter methods for its properties. A Bean class can be simply defined as a Java class that has a set of properties with corresponding getters and setters to access and modify these properties. The standard Java convention for Bean classes is to have a private field for each property, along with a public getter and setter method for that property.

Typically, a Java Bean is a Java class that follows certain naming conventions:

  • The property name should be the same as the field name, e.g., if the field is named firstName, the getter and setter methods should be named getFirstName() and setFirstName(String firstName), respectively.
  • The getter and setter methods should start with the “get” or “set” prefix, respectively, followed by the property name, converted to uppercase.
  • The getter method should return the type of the property, while the setter method should accept an argument of the same type.

Hutool, a Java toolkit, provides a simple way to determine whether a class is a Bean by checking if it has a single-parameter setter method.

Bean utility methods mainly involve operations on these getter and setter methods or conversion of Bean objects to other data structures, such as Maps.

Methods

Determining if a Class is a Bean

The BeanUtil.isBean method checks if a class has at least one setter method with a single parameter based on the standard Java Bean convention. This method can be used to determine whether a given class follows the Bean pattern.

Example usage:

boolean isBean = BeanUtil.isBean(HashMap.class); // false

Introspection

Introspection refers to inspecting an object’s internal state without using the object’s regular interface. It allows you to access an object’s properties using getter and setter methods even when these methods are not part of the object’s regular interface.

Hutool provides several utilities for introspection:

  1. BeanUtil.getPropertyDescriptors - retrieves an array of property descriptors for a given Bean class.
  2. BeanUtil.getFieldNamePropertyDescriptorMap - retrieves a map of field names to property descriptors for a given Bean class.
  3. BeanUtil.getPropertyDescriptor - retrieves the property descriptor for a specific property of a given Bean class.

Injecting Bean Properties

The BeanUtil.fillBean method is the core method for injecting properties into Bean objects. It takes a ValueProvider interface that provides access to values based on keys, as well as a CopyOptions parameter with additional configuration options for the property injection.

The CopyOptions parameter can include the following configuration options:

  • editable - limits the class or interface to be implemented by the target object, which must be an interface or superclass of the target object, used to restrict the copied properties. For example, if you want to copy only certain properties from a parent class, you can set this option to the parent class.
  • ignoreNullValue - specifies whether to ignore null values when copying properties. If set to true, any null values in the source object will be ignored during injection. If set to false, null values will be injected as null into the target object.
  • ignoreProperties - ignores specific properties listed in the array, any properties specified in this array will not be copied during injection.
  • ignoreError - specifies whether to ignore errors during property injection. If set to true, any errors encountered during injection will be ignored; otherwise, an error will be thrown if an error occurs during injection.

You can create a default CopyOptions object using the CopyOptions.create() method and adjust its settings using setter methods as needed.

The ValueProvider interface requires implementation of two methods:

  1. The value method is used to retrieve a value from anywhere using a key and target type, and convert it to the target type. If the returned value does not match the target type, the Convert.convert method will be automatically called for conversion.
  2. The containsKey method mainly detects whether the specified key is present. If the key is not present, the corresponding property will be ignored during injection.

First, define two beans:

// Lombok annotation
@Data
public class Person {
    private String name;
    private int age;
}

// Lombok annotation
@Data
public class SubPerson extends Person {
    public static final String SUBNAME = "TEST";

    private UUID id;
    private String subName;
    private Boolean isSlow;
}

Then inject this bean:

Person person = BeanUtil.fillBean(new Person(), new ValueProvider<String>() {

    @Override
    public Object value(String key, Class<?> valueType) {
        switch (key) {
            case "name":
                return "张三";
            case "age":
                return 18;
        }
        return null;
    }

    @Override
    public boolean containsKey(String key) {
        // There is always a key
        return true;
    }
    
}, CopyOptions.create());

Assert.assertEquals(person.getName(), "张三");
Assert.assertEquals(person.getAge(), 18);

Meanwhile, Hutool also provides the BeanUtil.toBean method. This is not for passing Bean objects, but for Bean classes, and Hutool will automatically call the default constructor to create an object.

Based on the BeanUtil.fillBean method, Hutool also provides Map object key-value pairs for injecting Beans. The methods include:

  1. BeanUtil.fillBeanWithMap uses a Map to populate the bean
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("name", "Joe");
map.put("age", 12);
map.put("openId", "DFDFSDFWERWER");

SubPerson person = BeanUtil.fillBeanWithMap(map, new SubPerson(), false);
  1. BeanUtil.fillBeanWithMapIgnoreCase uses a Map to populate the bean, ignoring case differences in keys
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("Name", "Joe");
map.put("aGe", 12);
map.put("openId", "DFDFSDFWERWER");
SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);

At the same time, it provides methods for converting maps to beans. Unlike fillBean, the difference here is that it does not pass Bean objects, but Bean classes, and Hutool will automatically create objects using the default constructor. Of course, the prerequisite is that the Bean class has a default constructor (empty constructor). These methods include:

  1. BeanUtil.toBean
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("a_name", "Joe");
map.put("b_age", 12);
// Set aliases for corresponding bean field names
HashMap<String, String> mapping = CollUtil.newHashMap();
mapping.put("a_name", "name");
mapping.put("b_age", "age");
Person person = BeanUtil.toBean(map, Person.class, CopyOptions.create().setFieldMapping(mapping));
  1. BeanUtil.toBeanIgnoreCase
HashMap<String, Object> map = CollUtil.newHashMap();
map.put("Name", "Joe");
map.put("aGe", 12);

Person person = BeanUtil.toBeanIgnoreCase(map, Person.class, false);

Converting Bean to Map

The BeanUtil.beanToMap method converts a Bean object to a Map object.

SubPerson person = new SubPerson();
person.setAge(14);
person.setOpenid("11213232");
person.setName("测试A11");
person.setSubName("sub名字");

Map<String, Object> map = BeanUtil.beanToMap(person);

Converting Bean to Bean

The conversion between Beans mainly involves copying the same attributes, so the method name is copyProperties. This method supports copying fields between Beans and Maps.

The BeanUtil.copyProperties method also provides a CopyOptions parameter for custom property copying.

SubPerson p1 = new SubPerson();
p1.setSlow(true);
p1.setName("测试");
p1.setSubName("sub测试");

Map<String, Object> map = MapUtil.newHashMap();

BeanUtil.copyProperties(p1, map);

Since version 5.6.6, adding conversion of Bean attributes in a copied list List conversion available using copyToList example:

List<Student> studentList = new ArrayList<>();
Student student = new Student();
student.setName("张三");
student.setAge(123);
student.setNo(3158L);
studentList.add(student);

Student student2 = new Student();
student.setName("李四");
student.setAge(125);
student.setNo(8848L);
studentList.add(student2);
// copy to Person class
List<Person> people = BeanUtil.copyToList(studentList, Person.class);

Alias Annotation

In Hutool 5.x, a custom annotation called @Alias has been added. This annotation can be used to set aliases for fields of a Bean.

First, let’s annotate a Bean:

// Lombok annotation
@Getter
@Setter
public static class SubPersonWithAlias {
    @Alias("aliasSubName")
    private String subName;
    private Boolean slow;
}
SubPersonWithAlias person = new SubPersonWithAlias();
person.setSubName("sub名字");
person.setSlow(true);

// When converting the Bean to a Map, the subName field is automatically modified to aliasSubName
Map<String, Object> map = BeanUtil.beanToMap(person);
// Returns "sub名字"
map.get("aliasSubName")

Similarly, the @Alias annotation supports aliases when injecting Beans:

Map<String, Object> map = MapUtil.newHashMap();
map.put("aliasSubName", "sub名字");
map.put("slow", true);

SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false);
// Returns "sub名字"
subPersonWithAlias.getSubName();