1. Bean Loading Principle
Loading process: through ResourceLoader and its sub-class DefaultResourceLoader to locate the location of Resource files, realize the function of locating Resource files from class path, file system, url and so on. After locating, the Resource object is obtained, and then handed to BeanDefinitionReader. It is then delegated to BeanDefinitionParserDelegate to complete the analysis of bean s and obtain BeanDefinition object, and then through registration. The rBeanDefinition method is registered. The ibu in the IOC container maintains a HashMap to save the BeanDefinition object. The BeanDefinition in Spring is actually the JavaBean we use.
What is BeanDefinition Object
BeanDefinition is an interface that describes a bean instance with attribute values, constructor parameter values, and more information provided by the implementation.

Pay attention to understanding the loading process
Before you start, you need to read and understand this process carefully. With this process, the difficulty of reading source code is less than half.
Most of the source codes are annotated, and some are official English annotations. Chinese is the main line (this article is also mainly through the main line), in order to be comprehensive, you need to explore again.
1. bean.xml
A common bean configuration file, here I want to emphasize the format inside it, because it will be used when parsing tags. It has labels such as < beans > `beans > `import > ``alias > etc. They are parsed and translated into BeanDefinition objects below.
<beans> <!-- this definition could be inside one beanRefFactory.xml file --> <bean id="a.qualified.name.of.some.sort" class="org.springframework.context.support.ClassPathXmlApplicationContext"> <property name="configLocation" value="org/springframework/web/context/beans1.xml"/> </bean> <!-- while the following two could be inside another, also on the classpath, perhaps coming from another component jar --> <bean id="another.qualified.name" class="org.springframework.context.support.ClassPathXmlApplicationContext"> <property name="configLocation" value="org/springframework/web/context/beans1.xml"/> <property name="parent" ref="a.qualified.name.of.some.sort"/> </bean> <alias name="another.qualified.name" alias="a.qualified.name.which.is.an.alias"/> </beans>
2. ResourceLoader.java
Policy interface for loading resources(Strategy mode).
DefaultResourceLoader is a standalone implementation that is usable outside an ApplicationContext, also used by ResourceEditorAn ApplicationContext is required to provide this functionality, plus extended ResourcePatternResolver support.
public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:". */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // Returns a Resource object (an object that specifies the location of the configuration file) Resource getResource(String location); // Return lassLoader of ResourceLoader @Nullable ClassLoader getClassLoader(); }
Then let's look at DefaultResourceLoader's implementation of the getResource() method.
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } // If location begins with/ if (location.startsWith("/")) { return getResourceByPath(location); } // If location begins with classpath: else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
As you can see, it judges three scenarios: / classpath: url format matching, and then calls the corresponding processing method. I only analyze classpath:, because this is the most commonly used. So take a look at the ClassPathResource implementation:
public ClassPathResource(String path, @Nullable 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()); }
Looking at the above code means that when you configure the static resource file path, you don't have to tangle with the classpath: you don't need to write /, because if you write it, it will filter it out for you.
So how do URLs locate?
Tracking getResourceByPath(location) method:
@Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); } // Here we use file system resource objects to define bean files return new FileSystemResource(path); }
Okay, obviously... we're off track, because what we want is xml file and path parsing, but fortunately, no change of soup or dressing. This will be covered below.
Triggering bean loading
Back to the point, when we use spring to load beans. XML manually, we use:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
Start with the ClassPathXmlApplicationContext class:
3. ClassPathXmlApplicationContext.java
There are only one constructor (multiple) and one getConfigResources() method in this class. The constructor eventually goes to the following constructor (adapter pattern):
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { // Dynamic determination of which loader to load the configuration file 1.super(parent); // Tell the reader where the profile is and locate the load profile 2.setConfigLocations(configLocations); // Refresh if (refresh) { // Before IOC containers are created, if they already exist, they need to be destroyed and closed to ensure refresh. //Later, a new IOC container was used. 3.refresh(); } }
Note: This class is critical, and I think it defines a Life Cycle for an xml loaded bean:
- The super() method completes the designation of the class loader.
- setConfigLocations(configLocations); Method locates and parses the configuration file to get the Resource object.
- refresh(); Method parses the label to get the BeanDefition object and registers it with the IOC container after checking. (Main research methods)
I marked 1.2.3. Correspond to the following method x, easy to read.
First, get to know setConfigLocations(configLocations) in depth.
Method 2. setConfigLocations(configLocations)
// Resolve Bean s to define resource file paths and process multiple resource file string arrays public void setConfigLocations(@Nullable String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { // resolvePath is a method of parsing strings into paths in the same class this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
Then we continue to look at the refresh() method of ClassPathXmlApplicationContext above:
Method 3. refresh()
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Preparing context for refresh prepareRefresh(); // Notify subclasses to refresh Bean factories ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Use this context to prepare the bean factory prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
Note: The following methods are all about refresh() in-depth reading, this method sets a very deep set, the following reading may cause discomfort.
Then look at the obtainFreshBeanFactory() method in refresh():
Method 3.1 obtainFreshBeanFactory()
// Call -- Refresh the bean factory protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // Delegation pattern: The parent class defines the refreshBeanFactory method, which specifically implements calling subclass containers refreshBeanFactory(); return getBeanFactory(); }
Then look at the refreshBeanFactory() method of obtainFreshBeanFactory().
Method 3.1.1 refreshBeanFactory()
// Refresh bean factory protected final void refreshBeanFactory() throws BeansException { // If there are containers, destroy and close them first if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // Create IOC containers DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); // Initialization of containers customizeBeanFactory(beanFactory); // Call the method defined by the loading Bean (using delegation mode) loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
Then follow the loadBeanDefinitions() method of refreshBeanFactory():
Method 3.1.1.1 loadBeanDefinitions()
Loading Bean Definition through XmlBean Definition Reader
// Loading Bean Definition through XmlBean Definition Reader @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // Create a new XmlBean Definition Reader for bean Factory XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); // Set up Spring resource loader for Bean reader (because grandfather class is a subclass of ResourceLoader, so is ResourceLoader) beanDefinitionReader.setResourceLoader(this); // Setting up SAX xml parser DOM4J for Bean reader beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. // Initialize BeanDefinition Reader initBeanDefinitionReader(beanDefinitionReader); // Real load bean definitions loadBeanDefinitions(beanDefinitionReader); }
Follow up the loadBean Definitions (XmlBean Definition Reader reader) method in the loadBean Definitions (DefaultListableBeanFactory BeanFactory) method:
Method 3.1.1.1.1 loadBeanDefinitions()
XMLBean reader loads BeanDefinition resources
// XMLBean Reader Loads Bean Definition Resources protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // Location of Bean Definition Resources Resource[] configResources = getConfigResources(); if (configResources != null) { // The XMLBean reader calls its parent AbstractBean DefinitionReader to read and locate Bean Definition Resources reader.loadBeanDefinitions(configResources); } // If the bean acquired in the subclass defines that the resource is positioned empty, // Get the resources set by the setConfigLocations method in the FileSystem Xml Application Context constructor String[] configLocations = getConfigLocations(); if (configLocations != null) { // The XMLBean reader calls its parent AbstractBean DefinitionReader to read and locate Bean Definition Resources reader.loadBeanDefinitions(configLocations); } }
@Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int count = 0; // for (Resource resource : resources) { count += loadBeanDefinitions(resource); } return count; }
Following loadBeanDefinitions(): This is just an abstract method to find the implementation of the XmlBeanDefinitionReader subclass:
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
Further into loadBean Definitions:
Loading bean s through explicit xml files
// Loading bean s through explicit xml files 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); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { // Converting resource files to the IO stream of InputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { // Obtaining xml parsing resources from streams InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { // Set encoding inputSource.setEncoding(encodedResource.getEncoding()); } // Specific reading process 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(); } } }
Further into doLoadBean Definitions ():
Really start loading BeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // Converting xml files to DOM objects Document doc = doLoadDocument(inputSource, resource); // Define the parsing process for beans, which uses Spring's bean configuration rules int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } ... ... .. }
The doLoadDocument() method parses the flow and returns a Document object: return builder.parse(inputSource); to avoid disrupting the idea, do it yourself.
You need to go further: registerBeanDefinitions()
Register BeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // Get the number of bean s registered in the container int countBefore = getRegistry().getBeanDefinitionCount(); // Parsing process entry, where delegation mode is used documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // Number of bean s for statistical parsing return getRegistry().getBeanDefinitionCount() - countBefore; }
Further into the registerBeanDefinitions() method (which is the result of the delegation pattern):
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { // Get the XML descriptor this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
Deep into doRegisterBean Definitions (doc. getDocumentElement ();:
Really start registering BeanDefinitions:
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. 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; } } } // Before the bean parses the definition, do a custom parse to see if it is a user-defined tag? preProcessXml(root); // Start parsing the document object defined by the bean parseBeanDefinitions(root, this.delegate); // After parsing the bean definition, customize the parsing to increase the scalability of the parsing process postProcessXml(root); this.delegate = parent; }
Next, look at parseBean Definitions (root, this. delegate);:
The root element of the document begins to parse and translate into Bean Definitions
// Starting with the root element of document, parse and translate it into Bean Definitions protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // The document object defined by the bean uses spring's default xml namespace if (delegate.isDefaultNamespace(root)) { // Gets all byte points of the document object root element defined by the bean NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // Getting the document node is an xml element node if (node instanceof Element) { Element ele = (Element) node; // The document element node defined by the bean uses spring's default xml namespace if (delegate.isDefaultNamespace(ele)) { // Resolving Element Nodes Using spring's bean Rules parseDefaultElement(ele, delegate); } else { // Instead of using spring's default xml namespace, user-defined parsing rules are used to parse element nodes delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // Parse < import > tag elements and import parsing if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // beans else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
ImportBean Definition Resource (ele), `process Alias Registration (ele), `process Bean Definition (ele, delegate)'; these three methods show the detailed process of tag parsing.
As you can see, it actually uses DOM4J to parse tags such as import bean alias, and then recursively within the tag until all the attributes are obtained and encapsulated in the BeanDefition object. For example, the processBeanDefinition method:
Give me an element to parse into Bean Definition
// Give me an element to parse into Bean Definition protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // Real Analytical Process BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. // Registration: Register db to ioc, delegation mode BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
Continue to dig deeper into registerBeanDefinition():
Register BeanDefinitions to the bean factory
// Register BeanDefinitions to the bean factory // Definition Holder: bean definition, including name and aliases // registry: Registered bean factory public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); // True registration registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
Further into registry. registerBeanDefinition (beanName, definition Holder. getBeanDefinition ());
Register BeanDefinitions to IOC Containers
Note: The class of this method is the interface, and what we're looking at is the method implemented by DefaultListableBeanFactory.java.
// Implement the BeanDefinition Registry interface and register BeanDefinitions @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"); // Is the check AbstractBean Definition? if (beanDefinition instanceof AbstractBeanDefinition) { try { // Markup bean Definition takes effect ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } // Determine whether the bean already exists in the bean Definition Map BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); //If there is no such bean if (existingDefinition != null) { //If bd is not allowed to overwrite registered bean s, an exception is thrown if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } // If overwriting is allowed, then the bean with the same name, registered overwriting is registered first. else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } // Register to a container, beanDefinitionMap is a container 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 (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }
This method checks the beans that need to be loaded and put s them into the bean Definition Map if there is no problem. The bean Definition Map is actually IOC. So our beans are loaded into the IOC container.