Spring source code parsing, configuration file reading, bean registration

Keywords: Programming xml Spring Attribute

Jar package for lock analysis: spring-beans-3.2.5.RELEASE.jar

How to read configuration files
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-test.xml"));
This is not recommended in spring 4.0, instead:
ClassPathXmlApplicationContext content = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
Source code analysis is almost the same.

The core of source code:

1. DefaultListableBeanFactory is the default implementation of registering and loading beans.

2. XML parsing

2.1 XmlBeanDefinitionReader is used to read XML configuration files. When reading XML configuration files, Spring actually abstracts different Resource paths into URL s, encapsulates the configuration files as resources, and looks at the construction method.

// Obtain the configuration file path, and finally convert it to ClassPathResource. ClassPathResource, as a class, inherits abstractfileresolvingsource, inherits AbstractResource, and implements the Resource interface.
    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
    }

//The process of creating XmlBeanFactory
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 {
        //The super in this place needs to pay attention to the fact that the constructor in AbstractAutowireCapableBeanFactory calls ignoreDependencyInterface() to ignore the corresponding dependency.
        super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
}

Class summary: mainly initialization. Look at the function of ignoreDependencyInterface() method: automatic assembly ignores the given dependent interface. For example, when there is attribute B in A, when getting the bean of A, if B is not loaded, spring will automatically initialize B, but in some cases B will not be initialized. For example: B implements the BeanNameAware interface. Automatic assembly is ignored. The typical Application is to resolve the Application context registration dependency through other ways, similar to BeanFactory injection through BeanNameAware or ApplicationContext injection through ApplicationContextAware.

2.2 see this.reader.loadBeanDefinitions(resource) for details; implementation of this method

//The upper level method mainly transforms resources into encoded resources and does nothing else. It mainly depends on this method.
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //In the previous paragraph, we mainly make some empty judgments to get the values to be passed.
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
                //Mainly look at this method
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

//This method is mainly: 1. Judge the file type. 2. Create Document objects according to different types. 3. Give it to the lower level
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
            //This mainly determines whether the xml file is of DTD or XSD type.
			int validationMode = getValidationModeForResource(resource);
            //Convert to unused Document object according to the obtained Document type
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            //Register bean definition
			return registerBeanDefinitions(doc, resource);
		}
     //. omit catch method 
}

//The function of this method: 1. Create BeanDefinitionDocumentReader. 2. Initialize XmlReaderContext. 3. Sub class implementation
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //Instantiate BeanDefinitionDocumentReader with DefaultBeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.setEnvironment(this.getEnvironment());
		int countBefore = getRegistry().getBeanDefinitionCount();
        //Create XmlReaderContext object and initialize namespaceHandlerResolver as the default DefaultNamespaceHandlerResolver. The implementation is handed to the subclass DefaultBeanDefinitionDocumentReader.
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

The main task of parsing XmlBeanDefinitionReader is to

1. Get the XML file validation mode.

2. Load the XML file and get the Document object.

3. Initialize BeanDefinitionDocumentReader and XmlReaderContext.

 

2.3 see the implementation class DefaultBeanDefinitionDocumentReader of BeanDefinitionDocumentReader, and do the specific things.

//Assign a readerContext to get the element of the Document
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}

//Still judging the parameters
protected void doRegisterBeanDefinitions(Element root) {
       //Processing profile properties
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			if (!this.environment.acceptsProfiles(specifiedProfiles)) {
				return;
			}
		}
        //Special processing and analysis
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(this.readerContext, root, parent);

        //There is an empty method reserved here. Users can analyze the pre-processing according to their needs.
		preProcessXml(root);
        //Mainly look at this method
		parseBeanDefinitions(root, this.delegate);
        //Processing of resolution meeting
		postProcessXml(root);

		this.delegate = parent;
	}

    //This method is to determine whether it is a default label or a custom label
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //Default label
		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;
					if (delegate.isDefaultNamespace(ele)) {
                        //Resolve the properties of the default label.
						parseDefaultElement(ele, delegate);
					}
					else {
                        //There are custom properties under the default label
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
            //Otherwise, it will be resolved according to the custom label.
			delegate.parseCustomElement(root);
		}
	}

    //Parsing four kinds of tags, import,alias,bean,beans,
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            //Parsing import Tags
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            //Parsing alias Tags
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            //Parsing of bean tags,
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse parsing beans
			doRegisterBeanDefinitions(ele);
		}
	}

See here, and finally enter the analysis of the tag. The profile attribute can deploy two sets of configurations in the configuration file to apply to the production environment and development environment at the same time.

Summarize the main tasks of the class.

1. Get all tags and judge whether they are default or custom tags.

2. Parse the attribute. If there is a custom attribute, re parse it.

3. Register the bean, send the response time, and notify the listener.

3. bean label parsing

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //Delegate the parseBeanDefinitionElement method of BeanDefinitionHolder class to parse the element and return BeanDefinitionHolder. After this method's processing, BeanDefinitionHolder has included various attributes in the configuration, such as class, id,name,alias and so on.
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
        //When the returned result of BeanDefinitionHolder is not empty, and it is found that there is a custom attribute under the default label, it will be parsed.
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
                //Register beans with beanName
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
            //Issue a response event to notify the relevant listener that the bean has been loaded.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

Posted by madmax on Fri, 18 Oct 2019 02:22:12 -0700