In the process of Spring Bean instance, how to use reflection and recursive processing of bean property filling?


Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

Catalogue of "Spring Handbook Column"

I. Introduction

超卖、掉单、幂等,你的程序总是不抗揍!

Think about it, the operation has been publicizing the activities for seven or eight days, and I am happily waiting for the page to go online on the last day. Suddenly there are a bunch of abnormalities, capital losses, and flashbacks, and the user traffic is fleeting, and I finally want to die. Have your heart!

As far as programming development is concerned, it’s a mess and messy code. Maybe this is the true portrayal of most junior programmers’ daily development. Even with testers’ verification, there will be bugs going online, but it’s just not there at the time. Found it! Because people write the code, there will be mistakes, even the old coders

As far as program bugs are concerned, it will include bugs in the product PRD process, bugs in operational configuration activities, bugs in function implementation during R&D and development, bugs in processes that were missed during testing and verification, and bugs in the configuration of operation and maintenance services during the online process. In fact, these can be gradually reduced as much as possible through the development of process specifications and certain R&D experience accumulation.

The other type is the bugs left by communication. Normally, business requirements are raised, product plans are determined, and R&D is implemented. Eventually, UI, testing, operation, architecture, etc. personnel are required to participate in the undertaking of a project. From development to online operation, it is actually difficult to maintain a unified information dissemination among this group of people. For example, in the middle of the project development, the operation stated a new requirement for the product, and the product felt that the function was not large, and then found the corresponding front-end development and logic, but I did not expect that it might also affect the back-end development and test use cases. Although the final function is online, it is not in the range of demand coverage of the entire production, research and testing, and it is invisible to bury a hole.

So, if you want to make your program very resistant to beatings and catch the farmer's three punches, then you have to do more than just a simple brick-moving farmer!

2. Goal

First of all, let’s review what we have accomplished in these chapters, including: implementing a container , defining and registering Beans , instantiating Beans , and implementing different instantiation strategies according to whether a constructor is included or not , so what is missing in creating object instantiation ? In fact, there is still a lack of information 类中是否有属性的问题. If a class contains attributes, then the attribute information needs to be filled in when instantiating, so that a complete object is created.

The filling of attributes is not only int, Long, String, but also object attributes that have not yet been instantiated. All of them need to be filled when the Bean is created. However, we will not consider the circular dependency of Bean for the time being, otherwise the entire function will be expanded, so that newcomers will not be able to grasp it when they learn. After the core functions are realized one after another in the follow-up, they will be gradually improved.

Three, design

Given the property is filled using Bean newInstanceor Cglibafter creation, began to fill the whole property information, you can be in the class AbstractAutowireCapableBeanFactoryto add up the whole property method createBean method. During the internship, you can also learn from the Spring source code. The implementation here is also a simplified version of Spring. The follow-up comparison study will be easier to understand.

  • After filling property class is instantiated to create, which is required in AbstractAutowireCapableBeanFactorythe process to add createBean applyPropertyValuesoperation.
  • Since we need to fill in property operations when creating a Bean, we need to add PropertyValues ​​information in the bean definition BeanDefinition class.
  • In addition, the filling attribute information also includes the object type of the Bean, that is, you need to define a BeanReference, which is actually a simple Bean name, which is created and filled recursively during the specific instantiation operation, which is the same as the Spring source code implementation. BeanReference in Spring source code is an interface

Fourth, realize

1. Engineering structure

small-spring-step-04
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── factory
    │           │   │   ├── BeanDefinition.java
    │           │   │   ├── BeanReference.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   ├── InstantiationStrategy.java
    │           │   │   └── SimpleInstantiationStrategy.java
    │           │   └── BeanFactory.java
    │           ├── BeansException.java
    │           ├── PropertyValue.java
    │           └── PropertyValues.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── UserDao.java
                │   └── UserService.java
                └── ApiTest.java

Project source code :公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring Bean container class relationship, as shown in Figure 5-2

Figure 5-2
  • In this chapter, three new classes need to be added, BeanReference(class reference), PropertyValue(attribute value), and PropertyValues(attribute collection), which are used for class and other type attribute filling operations.
  • In addition, the modified class is mainly AbstractAutowireCapableBeanFactoryto complete the attribute filling part in createBean.

2. Define attributes

cn.bugstack.springframework.beans.PropertyValue

public class PropertyValue {

    private final String name;

    private final Object value;

    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
    
    // ...get/set
}

cn.bugstack.springframework.beans.PropertyValues

public class PropertyValues {

    private final List<PropertyValue> propertyValueList = new ArrayList<>();

    public void addPropertyValue(PropertyValue pv) {
        this.propertyValueList.add(pv);
    }

    public PropertyValue[] getPropertyValues() {
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName)) {
                return pv;
            }
        }
        return null;
    }

}
  • The function of these two classes is to create a class for transferring attribute information in the class. Because there may be many attributes, it is also necessary to define a collection package.

3. Bean definition completion

cn.bugstack.springframework.beans.factory.config.BeanDefinition

public class BeanDefinition {

    private Class beanClass;

    private PropertyValues propertyValues;

    public BeanDefinition(Class beanClass) {
        this.beanClass = beanClass;
        this.propertyValues = new PropertyValues();
    }

    public BeanDefinition(Class beanClass, PropertyValues propertyValues) {
        this.beanClass = beanClass;
        this.propertyValues = propertyValues != null ? propertyValues : new PropertyValues();
    }
    
    // ...get/set
}
  • Bean information needs to be passed in the process of Bean registration, which is reflected in the tests in several previous chapters new BeanDefinition(UserService.class, propertyValues);
  • So in order to give the attribute to the Bean definition, the PropertyValues ​​attribute is filled here, and some simple optimizations are made to the two constructors to avoid the need to judge whether the attribute filling is empty in the for loop later.

4. Bean property filling

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            bean = createBeanInstance(beanDefinition, beanName, args);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
        Constructor constructorToUse = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
        for (Constructor ctor : declaredConstructors) {
            if (null != args && ctor.getParameterTypes().length == args.length) {
                constructorToUse = ctor;
                break;
            }
        }
        return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
    }

    /**
     * Bean 属性填充
     */
    protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        try {
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {

                String name = propertyValue.getName();
                Object value = propertyValue.getValue();

                if (value instanceof BeanReference) {
                    // A 依赖 B,获取 B 的实例化
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }
                // 属性填充
                BeanUtil.setFieldValue(bean, name, value);
            }
        } catch (Exception e) {
            throw new BeansException("Error setting property values:" + beanName);
        }
    }

    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }

}
  • The content of this class is a bit long, mainly including three methods: createBean, createBeanInstance, applyPropertyValues, here we mainly focus on the applyPropertyValues ​​method called in the createBean method.
  • In applyPropertyValues by acquisition beanDefinition.getPropertyValues()cycle attribute fill operation, if the encounter is BeanReference, then you need to get a recursive Bean instance, call getBean method.
  • When the dependent Bean object is created, it will recursively return to the current attribute filling. It should be noted here that we have not dealt with the problem of circular dependencies. This part is relatively large and will be added later. BeanUtil.setFieldValue(bean, name, value) is a method in the hutool-all tool class, you can also implement it yourself

Five, test

1. Prepare in advance

cn.bugstack.springframework.test.bean.UserDao

public class UserDao {

    private static Map<String, String> hashMap = new HashMap<>();

    static {
        hashMap.put("10001", "小傅哥");
        hashMap.put("10002", "八杯水");
        hashMap.put("10003", "阿毛");
    }

    public String queryUserName(String uId) {
        return hashMap.get(uId);
    }

}

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    private String uId;

    private UserDao userDao;

    public void queryUserInfo() {
        System.out.println("查询用户信息:" + userDao.queryUserName(uId));
    }

    // ...get/set
}
  • Dao and Service are the scenes we often use in our usual development. Inject UserDao into UserService, so that the dependency of Bean attributes can be reflected.

2. Test cases

@Test
public void test_BeanFactory() {
    // 1.初始化 BeanFactory
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();  

    // 2. UserDao 注册
    beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));   

    // 3. UserService 设置属性[uId、userDao]
    PropertyValues propertyValues = new PropertyValues();
    propertyValues.addPropertyValue(new PropertyValue("uId", "10001"));
    propertyValues.addPropertyValue(new PropertyValue("userDao",new BeanReference("userDao")));  

    // 4. UserService 注入bean
    BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues);
    beanFactory.registerBeanDefinition("userService", beanDefinition);    

    // 5. UserService 获取bean
    UserService userService = (UserService) beanFactory.getBean("userService");
    userService.queryUserInfo();
}
  • Unlike getting the Bean object directly, this time we also need to inject userDao into the Bean container first.beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));
  • Next is the operation of attribute filling, one is ordinary attribute new PropertyValue("uId", "10001"), and the other is object attributenew PropertyValue("userDao",new BeanReference("userDao"))
  • The next operation is simple, just get the userService object normally and call the method.

3. Test results

查询用户信息:小傅哥

Process finished with exit code 0

From the test results, we can see that our attribute filling has worked, because only after the attributes are filled can the Dao method be called, such as:userDao.queryUserName(uId)

Then we look at the Debug debugging, whether it has entered the implementation of Bean property filling, as follows:

  • Okay, here is the screenshot. We see that the property filling operation has started. When the property is found to be a BeanReference, you need to obtain and create a Bean instance.

Six, summary

  • In this chapter, we expand the object creation function in the AbstractAutowireCapableBeanFactory class. After the instantiation strategy that depends on whether there is a constructor is completed, we start to supplement the Bean attribute information. When the Bean attribute is a Bean object, recursive processing is required. Finally, reflection operations are needed when filling attributes, and some tool classes can also be used for processing.
  • We are implementing the function points of each chapter step by step, so that newcomers can better accept the design ideas in Spring. Especially in some already developed classes, the design of how to expand new functions is more important. Sometimes when learning programming, learning thinking design can improve programming thinking more than just simple implementation.
  • In this chapter, the development of the Bean creation operation is completed. Next, the resource attribute loading needs to be completed on the basis of the entire framework, that is, we need to move the Xml configuration, so that our small framework is more and more like Spring. In addition, all class names in the framework implementation process will refer to the Spring source code, and the corresponding design and implementation steps are also corresponding to the Spring source code, but it will simplify some processes, but you can use the same class name to search for each one The realization of the function in the Spring source code.

Seven, series recommendation