preface

This article is the second in the “How to Implement a Simple Version of Spring” series. In the first article, we introduced how to implement a simple Setter injection based on XML. In this article, we look at how to implement a simple Constructor injection function. The implementation steps follow the same “path” as Setter injection, where you design a data structure to parse and express the information in an XML configuration file, and then use those parsed data structures to do something like the Constructor injection here. Without further ado, let’s get straight to the point.

Data structure design

One configuration of XML using Constructor injection is as follows:

<bean id="orderService" class="cn.mghio.service.version3.OrderService">
    <constructor-arg ref="stockService"/>
    <constructor-arg ref="tradeService"/>
    <constructor-arg type="java.lang.String" value="mghio"/>
</bean>
Copy the code

The above OrderService class is as follows:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class OrderService {

    private StockDao stockDao;
    private TradeDao tradeDao;
    private String owner;

    public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {
        this.stockDao = stockDao;
        this.tradeDao = tradeDao;
        this.owner = owner; }}Copy the code

The configuration structure of XML is similar to that of Setter injection, which is in the form of a key-value class. Each constructor-arg node can be abstracted as a ValueHolder. Contains the actual parsed value type, type, and parameter name, as shown below:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class ValueHolder {
    private Object value;
    private String type;
    private String name;

    // omit setter and getter 
}    
Copy the code

The same Bean can contain multiple ValueHolders, which are further encapsulated as ConstructorArguments and provide CRUD interfaces to encapsulate the implementation and provide some judgment methods (such as whether constructor injection is configured or not). ValueHolder is an inner class, as shown below:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class ConstructorArgument {

    private final List<ValueHolder> argumentsValues = new LinkedList<>();

    public void addArgumentValue(Object value) {
        this.argumentsValues.add(new ValueHolder(value));
    }

    public List<ValueHolder> getArgumentsValues(a) {
        return this.argumentsValues;
    }

    public int getArgumentCount(a) {
        return this.argumentsValues.size();
    }

    public boolean isEmpty(a) {
        return this.argumentsValues.isEmpty();
    }

    public void clear(a) {
        this.argumentsValues.clear();
    }

    // some other methods...

    public static class ValueHolder {

        private Object value;
        private String type;
        privateString name; }}Copy the code

Then add a method to get ConstructorArgument and a method to determine whether ConstructorArgument is configured to the BeanDefinition interface. The structure is shown in the figure below:

Parse the XML configuration file

Parsing the XML from the previous article is also a bit easier, here we parse the constructor-arg node and assemble the data to add to BeanDefinition’s ConstructorArgument property, Modify the loadBeanDefinition(Resource Resource) method of the XmlBeanDefinitionReader class as follows:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class XmlBeanDefinitionReader {

    private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
    private static final String NAME_ATTRIBUTE = "name";
    private static final String TYPE_ATTRIBUTE = "type";

    // other fields and methods ...

    public void loadBeanDefinition(Resource resource) {
        try (InputStream is = resource.getInputStream()) {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(is);
            Element root = document.getRootElement();  // <beans>
            Iterator<Element> iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element element = iterator.next();
                String beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);
                String beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);
                BeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);
                if (null! = element.attributeValue(BEAN_SCOPE_ATTRIBUTE)) { bd.setScope(element.attributeValue(BEAN_SCOPE_ATTRIBUTE)); }// parse <constructor-arg> node
                parseConstructorArgElements(element, bd);
                parsePropertyElementValues(element, bd);
                this.registry.registerBeanDefinition(beanId, bd); }}catch (DocumentException | IOException e) {
            throw new BeanDefinitionException("IOException parsing XML document:"+ resource, e); }}private void parseConstructorArgElements(Element rootEle, BeanDefinition bd) {
        Iterator<Element> iterator = rootEle.elementIterator(CONSTRUCTOR_ARG_ELEMENT);
        while(iterator.hasNext()) { Element element = iterator.next(); parseConstructorArgElement(element, bd); }}private void parseConstructorArgElement(Element element, BeanDefinition bd) {
        String typeAttr = element.attributeValue(TYPE_ATTRIBUTE);
        String nameAttr = element.attributeValue(NAME_ATTRIBUTE);
        Object value = parsePropertyElementValue(element, null);
        ConstructorArgument.ValueHolder valueHolder = new ConstructorArgument.ValueHolder(value);
        if (StringUtils.hasLength(typeAttr)) {
            valueHolder.setType(typeAttr);
        }
        if (StringUtils.hasLength(nameAttr)) {
            valueHolder.setName(nameAttr);
        }
        bd.getConstructorArgument().addArgumentValue(valueHolder);
    }

    // other fields and methods ...

}
Copy the code

The overall XML parsing process is divided into two steps. The first step is to determine whether the

node exists as each

node is traversed, and the

node is resolved if it exists. The second step is to add the parser assembled ValueHolder to the BeanDefinition, so that we inject the Constructor of the XML configuration into the BeanDefinition, Let’s see how you can use this data structure for constructor injection during Bean creation.


How to choose Constructor

Obviously, using constructor injection needs to be placed in the instantiation Bean phase by determining whether the Bean currently being instantiated has constructor injection configured, and if so, using constructor instantiation. To determine whether XML has a configuration constructor injection, you can use the hasConstructorArguments() method provided by BeanDefinition, In fact is through the final judgment ConstructorArgument. ValueHolder set to determine whether there is a value. There is also the question of how to choose when there are multiple constructors. For example, the OrderService class has the following three constructors:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class OrderService {

    private StockDao stockDao;

    private TradeDao tradeDao;

    private String owner;

    public OrderService(StockDao stockDao, TradeDao tradeDao) {
        this.stockDao = stockDao;
        this.tradeDao = tradeDao;
        this.owner = "nobody";
    }

    public OrderService(StockDao stockDao, String owner) {
        this.stockDao = stockDao;
        this.owner = owner;
    }

    public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {
        this.stockDao = stockDao;
        this.tradeDao = tradeDao;
        this.owner = owner; }}Copy the code

The configuration of its XML constructor injection is as follows:

<bean id="orderService" class="cn.mghio.service.version3.OrderService">
    <constructor-arg ref="stockService"/>
    <constructor-arg ref="tradeService"/>
    <constructor-arg type="java.lang.String" value="mghio"/>
</bean>
Copy the code

How do you choose the best constructor for injection? The matching method used here is 1. Determine the number of constructor parameters first. If they do not match, skip directly and proceed with the next loop. 2. Check the parameter type when the number of constructor parameters matches. If the parameter type is consistent with the current parameter type or is the parent type of the current parameter type, use the constructor to instantiate the parameter. This is a fairly straightforward way to do it, and actually Spring’s way of doing it is more comprehensive and the code implementation is more complex, Interested friends can see org. Springframework. Beans. Factory. Support. ConstructorResolver. AutowireConstructor (…). Methods. Note that when parsing the XML configuration’s constructor injection parameters, convert the type to the target type. Name the class ConstructorResolver. If the Bean is instantiated using a constructor injection configuration, use the constructor injection configuration as follows:

/ * * *@author mghio
 * @sinceThe 2021-01-16 * /
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory.BeanDefinitionRegistry {

    // other fields and methods ...        

    private Object doCreateBean(BeanDefinition bd) {
        // 1. instantiate bean
        Object bean = instantiateBean(bd);
        // 2. populate bean
        populateBean(bd, bean);
        return bean;
    }

    private Object instantiateBean(BeanDefinition bd) {
        // Determine whether the current Bean's 'XML' configuration is configured for constructor injection
        if (bd.hasConstructorArguments()) {
            ConstructorResolver constructorResolver = new ConstructorResolver(this);
            return constructorResolver.autowireConstructor(bd);
        } else {
            ClassLoader classLoader = this.getClassLoader();
            String beanClassName = bd.getBeanClassName();
            try{ Class<? > beanClass =null; Class<? > cacheBeanClass = bd.getBeanClass();if (cacheBeanClass == null) {
                    beanClass = classLoader.loadClass(beanClassName);
                    bd.setBeanClass(beanClass);
                } else {
                    beanClass = cacheBeanClass;
                }
                return beanClass.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e); }}}// other fields and methods ...

}
Copy the code

At this point, you have implemented a simplified version of the XML-based configuration Constructor injection.

conclusion

This article briefly introduced Spring’s Constructor injection based on XML configuration. With the foundation of Setter injection in the first article, implementing Constructor injection is relatively easy. The implementation here is relatively simple, but the idea and general flow are similar. For more details on the Spring implementation, check out the source code. The complete code has been uploaded to GitHub, interested friends can go to mghio-Spring here to view the complete code, the next trailer: “How to implement a simple version of Spring – implementation of field annotation injection”.