JavaBean introspection and BeanInfo

Keywords: Java

Java's BeanInfo is rarely used at work. When I was studying the spring source code, I found that a genus called "spring.beaninfo.ignore" will be set when SpringBoot starts. This configuration can only be found on the Internet, which means whether to skip the search of java BeanInfo and find no other information, but what is BeanInfo?

JavaBean introduction

Wikipedia definition of JavaBeans: JavaBeans is a special class in Java, which can encapsulate multiple objects into one object (bean). It is characterized by serializability, providing a parameterless constructor, and providing getter and setter methods to access the properties of the object. "Bean" in the name is a common name for reusable software components for Java. To become a JavaBean class, you must follow specific specifications for naming, constructors, and methods. With these specifications, we can have tools that can use, reuse, replace and connect JavaBeans. The specifications are as follows:

  • There is a public parameterless constructor.
  • Properties can be accessed through get, set, is (which can be used instead of get on Boolean properties) or other methods that follow a specific naming convention.
  • Serializable.

The following is the definition of a legal JavaBean:

public class PersonBean implements java.io.Serializable {

    /**
     * name Properties (note case)
     */
    private String name = null;

    private boolean deceased = false;

    /** Parameterless constructor (no parameters) */
    public PersonBean() {
    }

    /**
     * name Getter method for property
     */
    public String getName() {
        return name;
    }

    /**
     * name Property
     * @param value
     */
    public void setName(final String value) {
        name = value;
    }

    /**
     * deceased Getter method for property
     * Different forms of Getter methods for Boolean properties (is instead of get is used here)
     */
    public boolean isDeceased() {
        return deceased;
    }

    /**
     * deceased Property
     * @param value
     */
    public void setDeceased(final boolean value) {
        deceased = value;
    }
}

Introspection of JavaBean

A simple spring MVC user login scenario is used to describe the introspection of JavaBean s. When users log in, the parameters passed in the front-end form are usually a Json string as follows:

{
	"username":"xxx",
	"password":"xxxx"
}

Where the back end accepts forms, you can usually use a JavaBean to receive parameters in the form of RequestBody:

public void login(@RequestBody LoginRequest request){
     // Do login
}

The LoginRequest is similar to the following format:

public class LoginRequest {
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private String username;
    private String password;
}

So how does the front-end Json map to the corresponding attribute in the back-end LoginRequest? You can see that the fields in LoginRequest are of private type, and the field value cannot be set directly (although reflection can be set, it is not appropriate). It can only be set through the Setter method, but how does the program know which Setter methods JavaBean has? The introspection mechanism of JavaBean is used here.

JavaBean introspection tool Introspector

The Java bean toolkit provides a java introspection tool Introspector, which can obtain the introspection result BeanInfo of java beans through the following methods (described in detail later). The process of obtaining BeanInfo is shown in the following figure

// You can choose to stop the retrieval in any parent class when the Object class is running
BeanInfo beanInfo = Introspector.getBeanInfo(JavaBeanDemo.class,Object.class);

JavaBean introspection result BeanInfo

Through the getBeanInfo method of Introspector, a java introspection tool, we can obtain the introspection BeanInfo of a JavaBean. The obtained BeanInfo contains the following properties:

  • Class related information of Bean
  • Event information of Bean
  • Attribute information of Bean
  • Bean's method information
  • Additional attribute information
  • Icon for Component

Type of introspection result BeanInfo

BeanInfo is just an interface for introspection results. There are three implementations of this interface in Java:

  1. ApplicationBeanInfo: Apple desktop related JavaBean introspection results
  2. ComponentBeanInfo: introspective results of Java Awt components, such as buttons
  3. GenericBeanInfo: General introspection results. All introspection results in JEE development are of this type

In addition, Spring has customized an introspective result type called ExtendedBeanInfo, which is mainly used to identify Setter methods whose return value is not empty.

Spring's BeanUtils.copyProperties

BeanUtils.copyProperties users copy the attributes between two objects. The underlying mechanism is based on JavaBean's introspection mechanism. By introspection, we can get the method of reading and writing the properties of the copy source object and the target object, and then call the corresponding method to copy the attributes. The following is the process of beanutils.copyproperties

BeanUtils optimizes some mechanisms of Java Bean introspection. Here, have you found some shortcomings of Java introspection?

Optimization of BeanUtils concurrency problem

The results of Java introspection are cached in the ThreadGroupContext, and the cache is locked through the synchonized keyword (the red box in the figure below), so that threads in the same thread group cannot introspect in parallel.

Spring's BeanUtils adds a layer of cache on top of Java introspection, which is implemented using ConcurrentHashMap, thus improving the efficiency of introspection.

BeanUtils Setter property identification optimization

In the default introspection process of Java, the return value of setter method must be null. If it is not null, it cannot be recognized as a valid JavaBean attribute (the red part in the figure below). Spring has customized a BeanInfo ExtendedBeanInfo to solve this problem.

spring.beaninfo.ignore

Back to spring.beaninfo.ignore mentioned earlier, this configuration is used to ignore the search of all custom BeanInfo classes

BeanUtils performance test

Copy method 10000 replications time consuming 1 million replications time consuming 100 million replications time consuming
ModelMapper copy 262mills 3875mills 283177mills
BeanUtils replication 3mills 369mills 20347mills
Direct replication Approximately equal to 0mills 5mills 438mills

It can be seen that BeanUtils takes about 50 times more time than direct replication.

public class BeanUtilsPerformanceTest {

    public static void main(String[] args){
        // Warm up virtual machine
        loopBeanUtils(100000);
        loopCopyByHand(100000);

        // 10000 copies
        System.out.println("\nloop 10000 times:");
        loopBeanUtils(10000);
        loopCopyByHand(10000);

        // 1 million copies
        System.out.println("\nloop 1000000 times:");
        loopBeanUtils(1000000);
        loopCopyByHand(1000000);

        // 100 million copies
        System.out.println("\nloop 100000000 times:");
        loopBeanUtils(100000000);
        loopCopyByHand(100000000);
    }

    private static void loopBeanUtils(int loopTimes){
        TestBeanDemo source = new TestBeanDemo();
        TestBeanDemo target = new TestBeanDemo();
        long start = System.currentTimeMillis();
        for (int i=0;i<loopTimes;i++){
            BeanUtils.copyProperties(source,target);
        }
        System.out.println("BeanUtils cost times:"+String.valueOf(System.currentTimeMillis()-start));
    }

    private static void loopCopyByHand(int loopTimes){
        TestBeanDemo source = new TestBeanDemo();
        TestBeanDemo target = new TestBeanDemo();
        long start = System.currentTimeMillis();
        for (int i=0;i<loopTimes;i++){
            target.setField1(source.getField1());
            target.setField2(source.getField2());
            target.setField3(source.getField3());
            target.setField4(source.getField4());
            target.setField5(source.getField5());
        }
        System.out.println("Copy field one by one times:"+String.valueOf(System.currentTimeMillis()-start));
    }

    @Data
    private static class TestBeanDemo{
        private String field1 = UUID.randomUUID().toString();
        private String field2 = UUID.randomUUID().toString();
        private String field3 = UUID.randomUUID().toString();
        private String field4 = UUID.randomUUID().toString();
        private String field5 = UUID.randomUUID().toString();

    }
}

I am the fox fox. Welcome to my WeChat official account: wzm2zsd

This article is first released to WeChat official account, all rights reserved, no reprint!

Posted by cyclefiend2000 on Tue, 07 Dec 2021 00:00:32 -0800