Spring source code analysis -- from Xml loading to parsing

Digression:

Interface & polymorphism

I have a bike and ride it to work every day

package com.zhao.SpringIoc;

public class Bike {
    public void go() {
        System.out.println("Go to work by bike");
    }
}

package com.zhao.SpringIoc;

/**
 * Version 1.0: handsome Zhao goes to work by bike
 */
public class ShuaiQiZhao {

    private static Bike bike;

    // private static Bus bus;

    // private static Car car;

    public static void main(String[] args) {
        bike = new Bike();
        bike.go();  // Go to work by bike
    }
}

After a while, I didn't want to feel too tired by bike, so I took the bus to work

package com.zhao.SpringIoc;

public class Bus {
    public void go() {
        System.out.println("Take the bus to work");
    }
}
package com.zhao.SpringIoc;

/**
 * Version 2.0: handsome Zhao goes to work by bus
 */
public class ShuaiQiZhao {
    
    // private static Bike bike;
    
     private static Bus bus;
     
    // private static Car car;

    public static void main(String[] args) {
        bus = new Bus();
        bus.go();  // Take the bus to work
    }
}

Before long, I had money and bought a car, so I drove to work every day

package com.zhao.SpringIoc;

public class Car {
    public void go() {
        System.out.println("Drive Martha to work");
    }
}
package com.zhao.SpringIoc;

/**
 * Version 3.0: handsome Zhao drives Martha to work
 */
public class ShuaiQiZhao {

    // private static Bike bike;

    // private static Bus bus;

    private static Car car;

    public static void main(String[] args) {
        car = new Car();
        car.go();  // Drive Martha to work
    }
}

When I buy a vehicle, I have to modify the member variable in the Person class and the vehicle in the main method. Some people think it's nothing? That's because you didn't write the code. It's more comfortable to see than to do! And if there were a thousand kinds of transportation, would you still say that?

Use polymorphism to improve the above situation

The housekeeper manages these vehicles

package com.zhao.SpringIoc;

public interface Movable {

    public void go();   // Abstract the common behavior of three vehicles (going to work)

}

Three vehicles implement this interface

package com.zhao.SpringIoc.Impl;

import com.zhao.SpringIoc.Movable;

public class Bike implements Movable {
    
    public void go() {
        System.out.println("Go to work by bike");
    }
    
}
package com.zhao.SpringIoc.Impl;

import com.zhao.SpringIoc.Movable;

public class Bus implements Movable {

    public void go() {
        System.out.println("Take the bus to work");
    }

}
package com.zhao.SpringIoc.Impl;

import com.zhao.SpringIoc.Movable;

public class Car implements Movable {

    public void go() {
        System.out.println("Drive Martha to work");
    }

}

Handsome Zhao found the housekeeper

package com.zhao.SpringIoc;

import com.zhao.SpringIoc.Impl.Car;

/**
 * Version 4.0: handsome Zhao drives Martha to work
 */
public class ShuaiQiZhao {

    private static Movable movable;

    public static void main(String[] args) {
        movable = new Car();
        movable.go();  // Drive Martha to work
    }
}

In contrast, in the past, if you wanted to use a vehicle, you had to declare its member variables and modify the class name and reference name of the main method.

Now just change the class name, isn't it very convenient?

But this is not the most convenient, that is, it has its own new object every time. But this is also helpless. Is there really no other way?

yes

What spring is good at is management. You can manage what objects you need. Traditional programming requires manual creation of various objects and internal control of objects. Spring gives the designed objects to container management (IOC). If you need anything, you can find it.

text

Spring objects are generated from XML documents or annotations. Let's not talk about annotations. It's too far from us. Let's start with XML.

Build environment

Interface: BookService

package com.zhao.Interface;

public interface BookService {
    /**
     * Book price
     * @return
     */
    double getBookPrice();
}

Interface: PressService

package com.zhao.Interface;

public interface PressService {
    /**
     * Return a sentence
     * @return
     */
    String say();
}

  Interface implementation class: BookServiceImpl

package com.zhao.service;

import com.zhao.Interface.BookService;


public class BookServiceImpl implements BookService {

    @Override
    public double getBookPrice() {
        return 508.8;
    }
}

Interface implementation class: PressServiceImpl

package com.zhao.service;

import com.zhao.Interface.BookService;
import com.zhao.Interface.PressService;

public class PressServiceImpl implements PressService {
    /**
     * Dependent BookService
     */
    private BookService bookService;

    /**
     * Place of dependency injection
     *
     * @param bookService
     */
    public void setBookService(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public String say() {
        return "The price of this book is:" + bookService.getBookPrice();
    }
}

Configuration file: applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!--BookService-->
    <bean id="bookService" class="com.zhao.service.BookServiceImpl">
    </bean>

    <!--PressService-->
    <bean id="pressService" class="com.zhao.service.PressServiceImpl">
        <!--Set dependencies: pressService rely on pressService-->
        <property name="bookService" ref="bookService"></property>
    </bean>
</beans>

Startup class: SourceCodeLearning

package com.zhao;

import com.zhao.Interface.PressService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;


public class SourceCodeLearning {

    public static void main(String[] args) {

        //Get the bean named user from the container
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        PressService pressService = (PressService) bf.getBean("pressService");

        //Method of calling bean
        String price = pressService.say();
        System.out.println(price);

    }
}

All right, here we go!

Let's start with the result analysis process. Now you think of yourself as Spring. What's the first thing to do? Load the configuration file to see which beans need to be created? Everything is difficult at the beginning. After taking the first step, it's easy to do now!

  1. Load profile
  2. Parsing configuration files
  3. Put the parsed Bean information into the Map collection

Load profile

I'm Spring now. I don't know what I'm going to face next. I don't know whether to load Xml files, JSON files, or yaml files. But I don't panic at all. Every configuration file should implement my rules, that is   Resource, which is now an Xml file, is used to load the Xml file   XmlBeanFactory

package org.springframework.beans.factory.xml;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.Resource;

@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
	
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
	
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
}

stay   What really works in the XmlBeanFactory class is   XmlBeanDefinitionReader (XBDR), which reads the beans in the Xml configuration file to me. At first I was curious about how it read, but I saw its father's friend Lao Wang   ResourceLoader, I knew Uncle Wang must have taught him his housekeeping skills. It wasn't long before xbdr   He came back with a pile of data, but no one could understand the dense data, so   XBDR   Invite your good friends   DocumentLoader   Help translate it into Doc documents (Java documents) and documentloader   Invite your good friends again   Bean definitionparserdelegate for label resolution.

  Since the process of loading and parsing is not important, only the flow chart is shown here. Specific source code, Debgger   Down. We are more concerned about where the parsed JavaBeans are stored and how the Ioc container is created!

Parsing of default tags by BeanDefinitionParserDelegate

package org.springframework.beans.factory.xml;

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    
    // BDDR's good friend, parsing Tags
	@Nullable
	private BeanDefinitionParserDelegate delegate;

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {

            // Parse import tag
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {

            // Parsing alias Tags
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {

            // Parsing bean Tags
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			
            // Parsing beans Tags
			doRegisterBeanDefinitions(ele);
		}
	}

    // Parsing bean Tags
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

        // Through this method, the bdHolder instance already contains various attributes configured in the configuration file, such as Class, name and id
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {

            //If there are custom attributes under the child nodes of the default label, the custom label will be resolved
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				
                //After parsing, register the parsed bdHolder
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			
            //Notify the relevant listener that the bean has been loaded
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

}

The source code of parsing tags should be simplified? Otherwise, the dense code looks like a headache!

public class BeanDefinitionParserDelegate {
@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

        // Resolution ID
		String id = ele.getAttribute(ID_ATTRIBUTE);
        // Resolve name
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        // Call the parseBeanDefinitionElement method to resolve other properties
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}
		return null;
	}

    @Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {

		this.parseState.push(new BeanEntry(beanName));

		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		String parent = null;
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}

		try {

            //Create a GenericBeanDefinition to host attributes, which corresponds to the Bean tag in the xml file
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
		    ...
			return bd;
		}
		finally {
			this.parseState.pop();
		}

		return null;
	}

}

Here we focus on  < Constructor Arg > tag, this   One is related to dependency injection, so the single actor comes out to explain.

< constructor Arg > tag

// Traverse, extract all constructor args, and then parse them
public class BeanDefinitionParserDelegate {
@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

       	public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {

                // Implement the specific parsing process
				parseConstructorArgElement((Element) node, bd);
			}
		}
	}
    

    // Implement the specific parsing process
	public void parseConstructorArgElement(Element ele, BeanDefinition bd) {

        // Extract index attribute
		String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);

        // Extract type attribute
		String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);

        // Extract name attribute
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		if (StringUtils.hasLength(indexAttr)) {
			try {
				int index = Integer.parseInt(indexAttr);
				if (index < 0) {
					error("'index' cannot be lower than 0", ele);
				}
				else {
					try {
						
						Object value = parsePropertyValue(ele, bd, null);
						ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
						if (StringUtils.hasLength(typeAttr)) {
							valueHolder.setType(typeAttr);
						}
						if (StringUtils.hasLength(nameAttr)) {
							valueHolder.setName(nameAttr);
						}
						valueHolder.setSource(extractSource(ele));
						
                      bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
						
					}
					finally {
						this.parseState.pop();
					}
				}
			}
			catch (NumberFormatException ex) {
				error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
			}
		}
		else {
			try {
				this.parseState.push(new ConstructorArgumentEntry());
				Object value = parsePropertyValue(ele, bd, null);
				ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
				if (StringUtils.hasLength(typeAttr)) {
					valueHolder.setType(typeAttr);
				}
				if (StringUtils.hasLength(nameAttr)) {
					valueHolder.setName(nameAttr);
				}
				valueHolder.setSource(extractSource(ele));
				  bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
			}
			finally {
				this.parseState.pop();
			}
		}
	}
}

The code above roughly means,

If the index is specified in the configuration file, the operation steps are as follows.

  1. analysis   Child element of constructor ARG
  2. use   ConstructorArgumentValues.ValueHolder type to encapsulate the resolved elements.
  3. Encapsulate the type, name and index in the ConstructorArgumentValues.ValueHolder type and add them to the current   Of BeanDefinition   Of ConstructorArgumentValues   IndexedArgumentValue property.

If the index attribute is not specified, the operation steps are as follows

  1. analysis   Child element of constructor ARG
  2. use   ConstructorArgumentValues.ValueHolder type to encapsulate the resolved elements.
  3. Encapsulate the type, name and index in the ConstructorArgumentValues.ValueHolder type and add them to the current   Of BeanDefinition   Of ConstructorArgumentValues   In the GenericArgumentValue property.

After the above tags are parsed, all you have to do is register. That is, DefaultBeanDefinitionDocumentReader   Method in class   BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		if (oldBeanDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

}

Register the bean in the register, with beanName as the key and beanDefinition as the value, and register it in the register(Map) collection.

The above code roughly means

  1. check
  2. Handle the case where beanName has been registered. If the override of the bean is not allowed, you need to throw an exception, otherwise you can override it directly
  3. Add map cache
  4. Clear the cache of the corresponding beanName left before resolution

Notification listener resolution

Method getReaderContext().fireComponentRegistered(new)   BeanComponentDefinition(bdHolder)); Implement notification listener parsing. spring currently does not do any logical processing for this event.

Tags: Java Spring

Posted on Wed, 13 Oct 2021 15:52:20 -0400 by laurajohn89