Accumulate over a long period, constant dripping wears away a stone 😄

  • Spring reading directory)

We already know that Spring will parse beans defined as
, @Bean, @Component, etc., into BeanDefinition objects. So how does Spring read an XML configuration file or how does Spring parse a method or class that’s annotated by an @bean annotation and end up as a BeanDefinition? (Note the pattern later)

BeanDefinitionReader

The BeanDefinitionReader reads the content of a Spring configuration file, resolves it to BeanDefinition, and registers it with the BeanDefinitionRegistry factory.


Let’s take a look at BeanDefinitionReader

public interface BeanDefinitionReader { 

	// Returns the bean factory used to register the bean definition
	BeanDefinitionRegistry getRegistry(a);

	// Resource loader, mainly used to return the corresponding Resource according to the given Resource file address
	@Nullable
	ResourceLoader getResourceLoader(a);

	// Return the classloader.
	@Nullable
	ClassLoader getBeanClassLoader(a);

	/ / BeanName generator
	// Generate a name for a bean that does not explicitly specify a bean name
	BeanNameGenerator getBeanNameGenerator(a);

	// Loads bean definitions from the specified resource, returning the number of bean definitions found
	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

	// Specify multiple resources to load bean definitions and return the number of bean definitions found
	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

	// Loads the bean definition from the specified resource location
	// The location can also be location mode, provided that the bean defines the reader's ResourceLoader as ResourcePatternResolver.
	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

	// Load multiple configuration file paths
	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
Copy the code

BeanDefinitionReader implementation class

BeanDefinitionReaderIs an interface, it has multiple implementation classes, so let’s see how many subclasses it has!You can seeBeanDefinitionReaderThere is an abstract subclassAbstractBeanDefinitionReader.AbstractBeanDefinitionReaderThere are three subclasses below.

Let’s look at another inheritance diagram:

  • AbstractBeanDefinitionReader: in order toBeanDefinitionReaderInterface abstract implementation class that implements EnvironmentCapable and provides methods for getting/setting the environment
  • XmlBeanDefinitionReader: read the XML file definedBeanDefinition
  • PropertiesBeanDefinitionReaderBeanDefinition can be read from properties files, Resource, Property objects, etc
  • GroovyBeanDefinitionReaderYou can read beans defined by the Groovy language

AbstractBeanDefinitionReader

This class implements abstract classes for the BeanDefinitionReader and EnvironmentCapable interfaces, providing properties: bean factories for registering bean definitions, resource loaders, classloaders to load bean classes, environments, and BeanName generators. Specific definitions are as follows:

// Register the bean factory defined by the bean
    private final BeanDefinitionRegistry registry;
// Resource loader
	@Nullable
	private ResourceLoader resourceLoader;
// Load the class loader for the bean class
	@Nullable
	private ClassLoader beanClassLoader;
/ / environment
	private Environment environment;
/ / BeanName generator
	private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
Copy the code

The core method of this class is loadBeanDefinitions(), so we’ll analyze it:

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int count = 0;
    String[] var3 = locations;
    int var4 = locations.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String location = var3[var5];
        count += this.loadBeanDefinitions(location);
    }

    return count;
}
Copy the code

When an array of resource locations is passed in, the loadBeanDefinitions(Location) method is iterated through. Its definition is as follows:

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(location, (Set)null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // Get the resource loader
    ResourceLoader resourceLoader = this.getResourceLoader();
  // Check whether the resource loader is empty and throw an exception if it is empty
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
        int count;
        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                // Call ResourcePatternResolver's getResources method based on the resource path, which can load multiple resources
                Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                LoadBeanDefinitions (Resource... loadBeanDefinitions(Resource... Resources) method
                count = loadBeanDefinitions(resource);
                // The method argument is passed in from the upper layer
                if(actualResources ! =null) {
                    Collections.addAll(actualResources, resources);
                }

                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }

                return count;
            } catch (IOException var6) {
                throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6); }}else {
            // This method can load only one resource
            Resource resource = resourceLoader.getResource(location);
           // Call the loadBeanDefinitions(Resources) method of the parent class and go through the different subclasses
            count = loadBeanDefinitions(resource);
            if(actualResources ! =null) {
                actualResources.add(resource);
            }

            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }

            returncount; }}}@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
	Assert.notNull(resources, "Resource array must not be null");
	int count = 0;
	for (Resource resource : resources) {
// Loop through the loadBeanDefinitions(Resources) method of the parent class and go through the different subclasses
		count += loadBeanDefinitions(resource);
	}
	return count;
}
Copy the code

Process the Resource path to return multiple or one resources, depending on the Resource loader, and then pass the resources as parameters to loadBeanDefinitions(Resource… Resources) method, which is used to handle multiple resources. Ultimately, call the BeanDefinitionReader#loadBeanDefinitions(resource) method, which is left to subclasses.

XmlBeanDefinitionReader

Such as an extension of the AbstractBeanDefinitionReader class, inherited AbstractBeanDefinitionReader all method, but also the extension of many new methods, mainly used to read XML documents defined in the bean. Specific use is as follows:

public static void main(String[] args) {
       // Set the loading environment
		//System.setProperty("spring.profiles.active", "dev");

		DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
		// Specify a specific subclass
		AbstractBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
		int i = reader.loadBeanDefinitions("spring-config.xml");
		System.out.println("Number of beans loaded this time: + i);
		// Get the Bean according to BeanName
		User user = (User)factory.getBean("user");
		System.out.println("user" + user);
		User.Vip vip = user.getVip();
		System.out.println("vip:" + vip.getVipLevel());

		// Get the Bean according to the alias
		User user2 = (User)factory.getBean("user2");
		System.out.println("user2:" + user2);
		User user4 = (User)factory.getBean("user4");
		System.out.println("user4:" + user4);
		// Get an array of aliases based on the bean name
		String[] users = factory.getAliases("user");
		for(String s : users) { System.out.println(s); }} result: ==== Number of beans loaded this time:1
userUser{name='null', address='null', VIP = com gongj. Beans. User VIP @ $2 ef1e4fa} VIP:9User2: User {name ='null', address='null', VIP =com.gongj.bean.User$Vip@2ef1e4fa} user4: User{name='null', address='null', vip=com.gongj.bean.User$Vip@2ef1e4fa}
user2
user3
user4
user7
user5
user6
Copy the code
  • The User object
public class User {
	public User(a) {}public User(String name, String address) {
		this.name = name;
		this.address = address;
	}

	@Override
	public String toString(a) {
		return "User{" +
				"name='" + name + '\' ' +
				", address='" + address + '\' ' +
				", vip=" + vip +
				'} ';
	}

	private String name;
	private String address;
	private Vip vip;
	public String getName(a) {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddress(a) {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public Vip getVip(a) {
		return vip;
	}

	public void setVip(Vip vip) {
		this.vip = vip;
	}


	// Internal Bean tests
	public class Vip {
		public String vipLevel;

		public String getVipLevel(a) {
			return vipLevel;
		}

		public void setVipLevel(String vipLevel) {
			this.vipLevel = vipLevel; }}}Copy the code
  • spring-config.xml

      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<! -- Can be based on,,; ,; Split up -->
	<bean  class="com.gongj.bean.User" name="user2,user5; user6,; user7" id="user">
		<property name="vip">
			<bean id="inner" class="com.gongj.bean.User$Vip">
				<constructor-arg ref="user"/>
				<property name="vipLevel" value="9"/>
			</bean>
		</property>
	</bean>
	<alias name="user2" alias="user3"></alias>
	<alias name="user2" alias="user4"></alias>

	<! --<beans profile="dev,; prd"> <bean id="user" class="com.gongj.bean.User" name="user2,; user5"></bean> <alias name="user2" alias="user3"></alias> <alias name="user2" alias="user4"></alias> </beans>-->
</beans>
Copy the code
  • pom.xml
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.6. RELEASE</version>
</dependency>
Copy the code

Let’s move on to our main exercise, XmlBeanDefinitionReader, which reads XML file configuration.


From this code into the reader. We wrote loadBeanDefinitions (” spring – config. XML “), As mentioned above, the BeanDefinitionReader#loadBeanDefinitions(resource) method is eventually called. The implementation of this method is left to subclasses, which we specified as XmlBeanDefinitionReader, When debugging goes into the subclass implementation, you can see that it loads Resource and encapsulates it as EncodedResource and specifies the encoding, of course its encoding and charset are both null.

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(new EncodedResource(resource)); >1
    }

 public EncodedResource(Resource resource) {
        this(resource, (String)null, (Charset)null);
    }
Copy the code

Then we go to the loadBeanDefinitions(EncodedResource EncodedResource) method, which obtains the InputSource object from the EncodedResource. The InputSource object parameters and Resource object parameters are passed to the underlying methods.

// The XML bean currently being loaded defines the resource, using ThreadLocal to avoid the resource being reloaded
	private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
			new NamedThreadLocal<>("XML bean definition resources currently being loaded");


public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}
		// Get the XML bean definition resource currently being loaded
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			// Allocate space and add it to it
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		// Check if currentResources contains encodedResource and throw an exception if it does not
		if(! currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			// Get the byte stream corresponding to Resource
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				//inputStream is an InputSource object
				InputSource inputSource = new InputSource(inputStream);
				// If the resource has a horse format, set the inputSource object to a horse format
				if(encodedResource.getEncoding() ! =null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// This method does further parsing and is key to creating a BeanDefinition
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); >1
			}
			finally{ inputStream.close(); }}catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			// Remove the XML bean definition resource currently being loaded
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove(); }}}Copy the code

Then go to the doLoadBeanDefinitions(InputSource, Resource Resource) method, This method basically converts the XML resource file into a Document object and registers the BeanDefinition against the Dcoument object

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
            // Parse the resource file into a Document object
			Document doc = doLoadDocument(inputSource, resource); >1
           // Register the Bean information based on the returned Dcoument
			int count = registerBeanDefinitions(doc, resource); >2
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from "+ resource, ex); }}Copy the code

Let’s look at doLoadDocument(InputSource InputSource, Resource Resource) method:

// Define the function to load from a resource file into a Document
private DocumentLoader documentLoader = new DefaultDocumentLoader();

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware()); >1
	}
Copy the code

DoLoadDocument method in the final call is documentLoader loadDocument () method. The call to this method takes five arguments:

  • InputSource: Resource file to call.
  • EntityResolver: Authentication mode for processing files.
  • ErrorHandler: indicates an ErrorHandler.
  • ValidationMode: validationMode for XML files.
  • NamespaceAware: Whether to enable automatic namespace awareness.

From documentLoader. LoadDocument () will click into the documentLoader interface, the interface under only one implementation: DefaultDocumentLoader. The specific call is as follows:

@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
		Create the DocumentBuilderFactory object
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); >1
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		// Create DocumentBuilder object
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); >2
		// Parse inputSource into a Document object
		return builder.parse(inputSource);
	}
Copy the code

This will parse the configuration in the XML into a Document object. You can use this Document object to get the nodes in the XML file and create the nodes.

CreateDocumentBuilderFactory (validationMode namespaceAware) : create DocumentBuilderFactory object

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {
		// Get the DocumentBuilderFactory instance
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(namespaceAware);
		// 2. Validate XML if XML validation is enabled
		if(validationMode ! = XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true);
			// If XML validation mode is XSD, it is mandatory to specify that the parser generated by this code will provide support for XML namespaces
			if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
				// Enforce namespace aware for XSD...
				factory.setNamespaceAware(true);
				try {
					factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
				}
				catch (IllegalArgumentException ex) {
					ParserConfigurationException pcex = new ParserConfigurationException(
							"Unable to validate using XSD: Your JAXP provider [" + factory +
							"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
							"Upgrade to Apache Xerces (or Java 1.5) for full XSD support");
					pcex.initCause(ex);
					throwpcex; }}}return factory;
	}
Copy the code

CreateDocumentBuilder (Factory, entityResolver, errorHandler) : Creates a DocumentBuilder object

protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
			@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
			throws ParserConfigurationException {
		// Create DocumentBuilder object
		DocumentBuilder docBuilder = factory.newDocumentBuilder();
		// If entityResolver is not empty, set entityResolver to docBuilder
		if(entityResolver ! =null) {
			docBuilder.setEntityResolver(entityResolver);
		}
		// If errorHandler is not empty, set errorHandler to docBuilder
		if(errorHandler ! =null) {
			docBuilder.setErrorHandler(errorHandler);
		}
		return docBuilder;
	}
Copy the code

Next we look at the registerBeanDefinitions(Doc, Resource) method

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        / / create BeanDefinitionDocumentReader object, complete BeanDefinition parsing and registered
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	    // The previous bd number
		int countBefore = getRegistry().getBeanDefinitionCount();
		Bd / / registration
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); >1
		// The number of BD registered this time
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
Copy the code

This method focuses on documentReader. RegisterBeanDefinitions (doc, createReaderContext (resource) method. Click enter to BeanDefinitionDocumentReader interface, the interface is to define the action of read Docuemnt BeanDefinition and registration.

The interface is an implementation class DefaultBeanDefinitionDocumentReader, then see DefaultBeanDefinitionDocumentReader registerBeanDefinitions method in class.

	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		doRegisterBeanDefinitions(doc.getDocumentElement()); >1
	}
Copy the code

This method takes two inputs:

  • Document: represents Spring configuration file information, obtained by BeanDefinitionReader resolution for Resrouce instances.

  • XmlReaderContext: Mainly contains BeanDefinitionReader and Resrouce

Then enter the doRegisterBeanDefinitions (doc. GetDocumentElement ()) method, parsing

protected void doRegisterBeanDefinitions(Element root) {
		// Create a delegate object that parses Element's various methods
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		// Validate the namespace of the XML file,
		/ / that determine whether they contain XMLNS = "http://www.springframework.org/schema/beans"
		if (this.delegate.isDefaultNamespace(root)) {
			
      
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				// Convert a String to a String[] array with the specified characters. If the String does not contain the specified characters, the entire String is put into the array.
				// If multiple characters are specified, they are separated by a single character.
				// string: "gong-jie/yuan"
				//	 *
				// * Specify characters: "-/"
				// * Return array: [gong, jie, yuan]
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// Check whether the specified environment is the same as the loaded configuration file
				if(! getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return; }}}// Preempting the null method
		preProcessXml(root);
		// Parse the method
		parseBeanDefinitions(root, this.delegate); >1
		// after the empty method
		postProcessXml(root);
		this.delegate = parent;
	}
Copy the code

Take a look at parseBeanDefinitions(root, this.delegate), the most important of these;

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		// Whether it is DefaultNamespace
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					// Parse the tag if it conforms to Spring's naming conventions.
					// instance 
      
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate); >1
					}
					else {
						// Parse user-defined rules
						// <tx:annotation-driven/>delegate.parseCustomElement(ele); }}}}else {
                // Parse user-defined rulesdelegate.parseCustomElement(root); }}Copy the code

The two methods of reading and parsing are quite different if you use Spring’s default configuration. Spring knows how to do this, of course, but if it is custom, then the user needs to implement some interface and configuration. For the root node or child node if the default namespace is parseDefaultElement, the parseDefaultElement method is used. Otherwise, use the delegate. ParseCustomElement method to parse the custom namespace. The way to determine whether a default namespace or a custom namespace is to use Node.getNamespaceuri to get the namespace, And fixed with Spring compare the namespace http://www.springframework.org/scherna/beans, if the agreement is considered to be the default, or you think is the custom.


The default tags are resolved in the parseDefaultElement(ele, delegate) function. The functions in the function are clear, and the four tags (import, Alias, bean, beans) are treated differently.

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		
      
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		
      
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		
      
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate); >1
		}
		// Parse the beans label
		/** * 
      
        * 
        * 
        * 
        * 
       */
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			/ / call doRegisterBeanDefinitions, repeat the process of parsing XMLdoRegisterBeanDefinitions(ele); }}Copy the code

BeanDefinitionReader The following blog posts will break down the parsing of various tags.


References:

The Spring IoC BeanDefinitionReader

BeanDefinitionReader- Executor that parses XML to BeanDefinition

18 – BeanDefinition Spring registration