Handwritten simplified spring --4 -- injecting properties and dependent objects

1, Target

First, let's review what these chapters have accomplished, including: implementing a container, defining and registering beans, instantiating beans, and implementing different instantiation strategies according to whether constructors are included. What are we missing in creating object instantiation? In fact, there is still a problem about whether there are attributes in a class. If a class contains attributes, you need to fill in the attribute information when instantiating, so as to create a complete object. For the filling of attributes, not only int, Long and String, but also object attributes that have not been instantiated, the filling operation needs to be carried out when the Bean is created. However, we will not consider Bean's circular dependency for the time being, otherwise we will expand the implementation of the whole function, so that newcomers can't grasp it when learning. After the core functions are implemented in succession, they will gradually improve it.

2, Design

Since the attribute filling starts to complete the attribute information after the Bean is created using newInstance or Cglib, you can add the complete attribute method in the createBean method of AbstractAutowireCapableBeanFactory.

  • After the class is instantiated and created, that is, the applyPropertyValues operation needs to be added in the createBean method of AbstractAutowireCapableBeanFactory.
  • Since we need to fill in property operations when creating beans, we need to add PropertyValues information in the bean definition class.
  • In addition, the filling attribute information also includes the object type of the Bean, that is, a BeanReference needs to be defined, which is actually a simple Bean name. It is created and filled recursively during specific instantiation operations, which is the same as the Spring source code implementation. In Spring source code, BeanReference is an interface

3, Realize

  1. engineering structure
  2. Class dependency graph

  1. Three new classes need to be added in this chapter, beanreference (class reference), propertyvalue (property value) and propertyvalues (property collection), which are respectively used for class and other types of property filling operations.
  2. In addition, the changed class is AbstractAutowireCapableBeanFactory, and the attribute filling part is completed in createBean.
  1. code
  • Define attribute class
public class PropertyValue {
    private final String name;
    private final Object value;
    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
    public String getName() {
        return name;
    }
    public Object getValue() {
        return value;
    }
}
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 transmitting attribute information in the class. Because there may be many attributes, you need to define a collection wrapper.
  • bean definition completion
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();
    }
    public Class getBeanClass() {
        return beanClass;
    }
    public void setBeanClass(Class beanClass) {
        this.beanClass = beanClass;
    }
    public PropertyValues getPropertyValues() {
        return propertyValues;
    }
    public void setPropertyValues(PropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }
}
  • In the process of Bean registration, it is necessary to transfer Bean information, which is reflected in the tests in several previous chapters. new BeanDefinition(UserService.class,propertyValues);
  • Therefore, in order to give the attribute to the Bean definition, the PropertyValues attribute is filled in here, and the two constructors are simply optimized to avoid judging whether the attribute filling is empty in the subsequent for loop.
  • Class reference
public class BeanReference {
    private final String beanName;
    public BeanReference(String beanName) {
        this.beanName = beanName;
    }
    public String getBeanName() {
        return beanName;
    }
}
  • bean property filling
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);
            // Populate the Bean with properties
            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 Attribute filling
     */
    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 you encounter a BeanReference, you need to recursively obtain the Bean instance and call the getBean method.
                if (value instanceof BeanReference) {
                    // A depends on B to obtain the instantiation of B
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }
                // Attribute filling
                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;
    }
}
  1. The content of this class is slightly longer, mainly including three methods: createBean, createBeanInstance, applyPropertyValues, where we mainly focus on the applyPropertyValues method invoked in createBean's method.
  2. In applyPropertyValues, by obtaining beandefinition Getpropertyvalues() loop performs the property filling operation. If a BeanReference is encountered, you need to recursively obtain the Bean instance and call the getBean method.
  3. When the dependent Bean object is created, it will be returned to the current attribute filling. It should be noted that we did not deal with the problem of circular dependency. This part is relatively large and will be supplemented later. BeanUtil.setFieldValue(bean, name, value) is a method in the hutool all tool class. You can also implement it yourself
  • prepare
public class UserDao {
    private static Map<String, String> hashMap = new HashMap<>();
    static {
        hashMap.put("10001", "Little brother Fu");
        hashMap.put("10002", "Eight cups of water");
        hashMap.put("10003", "A Mao");
    }
    public String queryUserName(String uId) {
        return hashMap.get(uId);
    }
}
public class UserService {
    private String uId;
    private UserDao userDao;
    public void queryUserInfo() {
        System.out.println("Query user information:" + userDao.queryUserName(uId));
    }
    public String getuId() {
        return uId;
    }
    public void setuId(String uId) {
        this.uId = uId;
    }
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

4, Testing

    @Test
    public void test_BeanFactory() {
        // 1. Initialize BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 2. UserDao registration
        beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));
        
        // 3. UserService setting attribute [uId, userDao]
        PropertyValues propertyValues = new PropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("uId", "10001"));
        propertyValues.addPropertyValue(new PropertyValue("userDao",new BeanReference("userDao")));
        
        // 4. UserService injection bean
        BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues);
        beanFactory.registerBeanDefinition("userService", beanDefinition);
        
        // 5. UserService gets the bean(UserService depends on Userdao)
        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.queryUserInfo();
    }
  1. Different from directly obtaining the Bean object, this time we also need to inject userdao into the Bean container beanfactory registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));
  2. Next is the operation of property filling. One is the general property newPropertyValue("uId", "10001"), and the other is the object property new PropertyValue("userDao",new BeanReference("userDao"))
  3. The next operation is simple, just get the userService object normally and call the method.

If you unregister UserDao, the following information will be reported

5, Summary

  1. In this chapter, we extend the function of creating objects in the AbstractAutowireCapableBeanFactory class, which depends on whether there is a constructor. After the instantiation strategy is completed, we begin to supplement the Bean attribute information. When the Bean attribute is a Bean object, recursive processing is required. Finally, reflection operations are required when filling attributes, and some tool classes can also be used.
  2. We are implementing the function points of each chapter step by step, so that new people can better accept the design ideas in Spring. Especially in some developed classes, how to expand new functions is more important. Learning programming sometimes learning design ideas can improve programming thinking more than just doing simple implementation.
  3. In this chapter, the Bean creation operation is completed. Next, we need to load the resource attributes based on the whole framework, that is, we need to move the Xml configuration to make our small framework more and more like Spring. In addition, in the process of framework implementation, all class names will refer to the Spring source code, and the corresponding design and implementation steps are also corresponding to the Spring source code, which will only simplify some processes, but you can take the same class name to find the implementation of each function in the Spring source code.

Added by mslinuz on Fri, 24 Dec 2021 16:13:58 +0200