Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the loading process of the Spring IOC container

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the relevant knowledge of "how the loading process of Spring IOC container is". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Loading process of spring ioc container

* * 1. Goal: * * be proficient in using spring, analyze its source code and understand the ideas in it. This article mainly introduces the loading of spring ioc container

* * 2. Prerequisite: * * debug will be used

* 3. Source code analysis method: * * Source code traceability in Intellj idea debug mode

Read the xml components through ClassPathXmlApplicationContext, and read the running information of the program from each stack

* * 4. Note: * * because Spring's class inheritance system is complex and cannot be fully mapped, only the most important class inheritance structure class diagram found after analyzing the source code is posted below.

* * 5. About Spring Ioc

Demo:** We start with demo and do code traceability step by step.

Spring Ioc Demo

1. Define the data access interface IUserDao.java

Public interface IUserDao {public void InsertUser (String username,String password);}

two。 Define the IUserDao.java implementation class IUserDaoImpl.java

Public class UserDaoImpl implements IUserDao {@ Override public void InsertUser (String username, String password) {System.out.println ("- UserDaoImpl-- addUser----");}}

3. Define the business logic interface UserService.java

Public interface UserService {public void addUser (String username,String password);}

4. Define the UserService.java implementation class UserServiceImpl.java

Public class UserServiceImpl implements UserService {private IUserDao userDao; / / set method public void setUserDao (IUserDao userDao) {this.userDao = userDao;} @ Override public void addUser (String username,String password) {userDao.InsertUser (username,password);}}

Bean.xml profile

ApplicationContext inheritance structure

1. Top-level interface: ApplicationContext

The 2.ClassPathXmlApplicationContext implementation class inherits the AbstractXmlApplication abstract class

3.AbstractXmlApplication inherits AbstractRefreshableConfigApplicationContext

The 4.AbstractRefreshableConfigApplicationContext abstract class inherits from AbstractRefreshableApplicationContext

5.AbstractRefreshableApplicationContext inherits AbstractApplicationContext

Implementing ConfigurableApplicationContext Interface with 6.AbstractApplicationContext

7.ConfigurableApplicationContext interface inheritance

ApplicationContext interface

Generally speaking, the inheritance implementation structure is deep, and a large number of adapter patterns are used internally.

Take ClassPathXmlApplicationContext as an example, the inheritance class diagram is shown in the following figure:

Detailed explanation of the source code in the loading process of Spring Ioc container

Before we begin, let's introduce the concept of a whole. That is, the loading of the spring ioc container generally goes through the following processes:

Resource file location, parsing, registration, instantiation

1. Resource file location

The location of the resource file is generally completed in the implementation class of ApplicationContext, because the ApplicationContext interface inherits the ResourcePatternResolver interface, the ResourcePatternResolver interface inherits the ResourceLoader interface, and the getResource () method in ResourceLoader can read external resources as Resource classes.

two。 Parsing DefaultBeanDefinitionDocumentReader

Parsing is mainly done in BeanDefinitionReader, and the most commonly used implementation class is XmlBeanDefinitionReader, where the loadBeanDefinitions () method is responsible for reading the Resource and completing the next steps. After ApplicationContext finishes locating the resource file, it entrusts the parsing work to XmlBeanDefinitionReader.

Parsing involves a number of steps, and in the most common case, the resource file comes from an XML configuration file. The first is BeanDefinitionReader, which reads the XML file into a W3C Document document.

DefaultBeanDefinitionDocumentReader further parses the Document. DefaultBeanDefinitionDocumentReader then delegates parsing to BeanDefinitionParserDelegate. If it is a standard xml namespace element, the parsing will be completed within the Delegate. If it is a non-standard xml namespace element, the appropriate NamespaceHandler will be delegated for parsing. The final parsing result will be encapsulated as BeanDefinitionHolder, so the parsing is complete.

It will be explained in detail later.

3. Register

Then the registration of bean is completed in BeanFactory. One of the most common implementation classes of the BeanFactory interface is DefaultListableBeanFactory, which implements the BeanDefinitionRegistry interface, so the registerBeanDefinition () method can register the BeanDefinition. Here, the most common XmlWebApplicationContext does not own the BeanDefinition, it inherits from AbstractRefreshableApplicationContext, it holds a DefaultListableBeanFactory field, which is used to save the BeanDefinition.

The so-called registration, in fact, is to save the name and instances of BeanDefinition to a Map. As I just mentioned, the most commonly used implementation of DefaultListableBeanFactory, where the field is beanDefinitionMap, is a ConcurrentHashMap.

The code is as follows:

1.DefaultListableBeanFactory inheritance implementation relationship

Public class DefaultListableBeanFactoryextends AbstractAutowireCapableBeanFactory implementsConfigurableListableBeanFactory, BeanDefinitionRegistry,Serializable {/ / DefaultListableBeanFactory instances finally save all registered bean beanDefinitionMap / * * Map of bean definition objects, keyed by bean name * / private final Map beanDefinitionMap = new ConcurrentHashMap (64); / / implement the registerBeanDefinition () abstract method public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException defined in BeanDefinitionRegistry {}

> 2.BeanDefinitionRegistry interface

Public interface BeanDefinitionRegistry extends AliasRegistry {/ / defines the abstract method void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException for registering BeanDefinition instances

4. Instantiation

After the registration is also completed, initialization, that is, dependency injection, is completed in the getBean () method of BeanFactory

In general, this is the process.

Refresh () method

1. Goal:

This record of debug traceability source code process, probably divided into three pages, this is the first article, now an overall understanding of the running process, location of resource loading, resource parsing, bean registration occurs.

two。 Record structure:

1. Debug stack screenshot

two。 Overall process

The treatment of 3.bean.xml

There is a corresponding explanation at the bottom of each code

Debug stack screenshot

The line number of the method in each stack frame is marked, and the source code can be traced back according to the line number, and then you can learn quickly with the tutorial.

Overall process

Ioc container instantiation code

ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("bean.xml")

Step by step into the code traced, found an important method: refresh ()

As follows:

Public void refresh () throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {/ / Prepare this context for refreshing. PrepareRefresh (); / / beanFactory instantiation method single-step debugging entry / / Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory (); / / Prepare the bean factory for use in this context. 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) {/ / Destroy already created singletons to avoid dangling resources. DestroyBeans (); / / Reset 'active' flag. CancelRefresh (ex); / / Propagate exception to caller. Throw ex;}

First of all, this method is synchronized to avoid repeated refreshes. Then each step of the refresh is put in a separate method, which is relatively clear and can be viewed one by one in order.

The first is the prepareRefresh () method

Protected void prepareRefresh () {this.startupDate = System.currentTimeMillis (); synchronized (this.activeMonitor) {this.active = true;} if (logger.isInfoEnabled ()) {logger.info ("Refreshing" + this);} / / Initialize any placeholder property sources in the context environment initPropertySources () / / Validate that all properties marked as required are resolvable / / see ConfigurablePropertyResolver#setRequiredProperties this.environment.validateRequiredProperties ();}

Not much is done in this method, the start time is recorded, the output log is recorded, and the initPropertySources () method and validateRequiredProperties () method generally do nothing.

Then there is the core obtainFreshBeanFactory () method, which initializes the BeanFactory and is the core of the entire refresh () method, which completes the loading, parsing, and registration of the configuration file, which will be discussed in more detail later.

To explain here, ApplicationContext implements the BeanFactory interface, and implements interfaces such as ResourceLoader, MessageSource, and so on, which can be considered as enhanced BeanFactory. However, ApplicationContext does not repeat the methods defined by BeanFactory itself, but delegates it to DefaultListableBeanFactory. This kind of design idea is also worth learning.

The following methods, such as prepareBeanFactory (), postProcessBeanFactory (), invokeBeanFactoryPostProcessors (), registerBeanPostProcessors (), initMessageSource (), initApplicationEventMulticaster (), onRefresh (), registerListeners (), finishBeanFactoryInitialization (), finishRefresh (), etc., add some post-processors, broadcasts, interceptors, etc., so they don't have to go into details one by one.

The key method is finishBeanFactoryInitialization (), in which the Bean just registered (loaded without delay) is instantiated, so it is also a core method.

The treatment of bean.xml

After introducing the process as a whole, we will focus on the obtainFreshBeanFactory () method, in which the loading, parsing and registration of configuration files are completed.

Protected ConfigurableListableBeanFactory obtainFreshBeanFactory () {refreshBeanFactory (); ConfigurableListableBeanFactory beanFactory = getBeanFactory (); if (logger.isDebugEnabled ()) {logger.debug ("Bean factory for" + getDisplayName () + ":" + beanFactory);} return beanFactory;}

This method does two things. First, an instance of DefaultListableBeanFactory is created and initialized through the refreshBeanFactory () method.

Protected final void refreshBeanFactory () throws BeansException {if (hasBeanFactory ()) {destroyBeans (); closeBeanFactory ();} try {DefaultListableBeanFactory beanFactory = createBeanFactory (); beanFactory.setSerializationId (getId ()); customizeBeanFactory (beanFactory); loadBeanDefinitions (beanFactory); synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory }} catch (IOException ex) {throw new ApplicationContextException ("I error parsing bean definition source for O error parsing bean definition source for" + getDisplayName (), ex);}}

First, if you already have an instance of BeanFactory, clear it first. Then create an instance of DefaultListableBeanFactory through the createBeanFactory () method

Protected DefaultListableBeanFactory createBeanFactory () {return new DefaultListableBeanFactory (getInternalParentBeanFactory ());}

Next, set the ID unique identity.

BeanFactory.setSerializationId (getId ())

Then allow the user to make some custom configurations

Protected void customizeBeanFactory (DefaultListableBeanFactory beanFactory) {if (this.allowBeanDefinitionOverriding! = null) {beanFactory.setAllowBeanDefinitionOverriding (this.allowBeanDefinitionOverriding);} if (this.allowCircularReferences! = null) {beanFactory.setAllowCircularReferences (this.allowCircularReferences);} beanFactory.setAutowireCandidateResolver (new QualifierAnnotationAutowireCandidateResolver ());}

Finally, there is the core loadBeanDefinitions () method

Protected void loadBeanDefinitions (DefaultListableBeanFactory beanFactory) throws BeansException, IOException {/ / Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader (beanFactory); / / Configure the bean definition reader with this context's / / resource loading environment. BeanDefinitionReader.setEnvironment (this.getEnvironment ()); beanDefinitionReader.setResourceLoader (this); beanDefinitionReader.setEntityResolver (new ResourceEntityResolver (this)); / / Allow a subclass to provide custom initialization of the reader, / / then proceed with actually loading the bean definitions. InitBeanDefinitionReader (beanDefinitionReader); loadBeanDefinitions (beanDefinitionReader);}

Here you will first create an instance of XmlBeanDefinitionReader and then initialize it. Why can this instance of type BeanDefinitionRegistry that is actually passed in XmlBeanDefinitionReader pass a beanFactory? because DefaultListableBeanFactory implements the BeanDefinitionRegistry interface, which is the use of polymorphism.

Protected void loadBeanDefinitions (DefaultListableBeanFactory beanFactory) throws BeansException, IOException {/ / Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader (beanFactory); / / Configure the bean definition reader with this context's / / resource loading environment. BeanDefinitionReader.setEnvironment (this.getEnvironment ()); beanDefinitionReader.setResourceLoader (this); beanDefinitionReader.setEntityResolver (new ResourceEntityResolver (this)); / / Allow a subclass to provide custom initialization of the reader, / / then proceed with actually loading the bean definitions. InitBeanDefinitionReader (beanDefinitionReader);}

To be clear here, ApplicationContext is not responsible for loading, parsing, and registering configuration files on its own, but rather entrusts these tasks to XmlBeanDefinitionReader.

LoadBeanDefinitions (beanDefinitionReader)

This line of code is where the Bean definition read actually occurs. The work here is mainly done by XmlBeanDefinitionReader, and the next blog post will describe this process in detail.

LoadBeanDefinitionsloadBeanDefinitions: source code reading

The entry is the loadBeanDefinitions method

Protected void loadBeanDefinitions (XmlBeanDefinitionReader reader) throws IOException {String [] configLocations = getConfigLocations (); if (configLocations! = null) {for (String configLocation: configLocations) {reader.loadBeanDefinitions (configLocation);}

This is the outermost code of the parsing process, starting with getting the path to the configuration file, which has been done before.

Then pass the path of each configuration file as a parameter to the loadBeanDefinitions method of BeanDefinitionReader

Public int loadBeanDefinitions (String location) throws BeanDefinitionStoreException {return loadBeanDefinitions (location, null);}

This method calls the overloaded method again.

Public int loadBeanDefinitions (String location, Set actualResources) throws BeanDefinitionStoreException {ResourceLoader resourceLoader = getResourceLoader (); if (resourceLoader = = null) {throw new BeanDefinitionStoreException ("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");} if (resourceLoader instanceof ResourcePatternResolver) {/ / Resource pattern matching available. Try {Resource [] resources = ((ResourcePatternResolver) resourceLoader) .getResources (location); int loadCount = loadBeanDefinitions (resources); if (actualResources! = null) {for (Resource resource: resources) {actualResources.add (resource) }} if (logger.isDebugEnabled ()) {logger.debug ("Loaded" + loadCount + "bean definitions from location pattern [" + location + "]");} return loadCount } catch (IOException ex) {throw new BeanDefinitionStoreException ("Could not resolve bean definition resource pattern [" + location + "]", ex);}} else {/ / Can only load single resources by absolute URL. Resource resource = resourceLoader.getResource (location); int loadCount = loadBeanDefinitions (resource); if (actualResources! = null) {actualResources.add (resource);} if (logger.isDebugEnabled ()) {logger.debug ("Loaded" + loadCount + "bean definitions from location [" + location + "]");} return loadCount }}

First of all, the prerequisite for the implementation of getResourceLoader () is that XmlBeanDefinitionReader has determined to create an instance ResourceLoader instance when instantiating, and the code is located in AbstractBeanDefinitionReader

Protected AbstractBeanDefinitionReader (BeanDefinitionRegistry registry) {Assert.notNull (registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; / / Determine ResourceLoader to use. If (this.registry instanceof ResourceLoader) {this.resourceLoader = (ResourceLoader) this.registry;} else {this.resourceLoader = new PathMatchingResourcePatternResolver ();} / Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) {this.environment = ((EnvironmentCapable) this.registry). GetEnvironment ();} else {this.environment = new StandardEnvironment () }}

This method is rather long, and BeanDefinitionReader cannot load the configuration file directly. You need to encapsulate the configuration file into Resource before you can call the overloaded method loadBeanDefinitions (). So this method is actually two segments. The first part is to entrust ResourceLoader to encapsulate the configuration file into Resource, and the second part is to call loadBeanDefinitions () to parse Resource.

And the ResourceLoader here is the previous XmlWebApplicationContext, because the ApplicationContext interface inherits from the ResourceLoader interface.

Resource is also an interface system. In the web environment, this is ServletContextResource.

Next, enter the overloaded method loadBeanDefinitions ()

Public int loadBeanDefinitions (Resource... Resources) throws BeanDefinitionStoreException {Assert.notNull (resources, "Resource array must not be null"); int counter = 0; for (Resource resource: resources) {counter + = loadBeanDefinitions (resource);} return counter;}

Needless to say here, take each Resource as a parameter and continue to call the overloaded method. If you read the spring source code, you will find that there are many overloading methods.

Public int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions (new EncodedResource (resource));

It is still the overloaded method, but here the incoming Resource is encapsulated again and becomes the encoded Resource.

Public int loadBeanDefinitions (EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull (encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled ()) {logger.info ("Loading XML bean definitions from" + encodedResource.getResource ());} Set 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 {InputStream inputStream = encodedResource.getResource (). GetInputStream (); try {InputSource inputSource = new InputSource (inputStream) If (encodedResource.getEncoding ()! = null) {inputSource.setEncoding (encodedResource.getEncoding ());} 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 is the last overloaded method of loadBeanDefinitions (), which is long and can be taken a look at.

Assert.notNull (encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled ()) {logger.info ("Loading XML bean definitions from" + encodedResource.getResource ());} Set 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!");}

The first part is to deal with thread-related work, setting the Resource currently being parsed to the current Resource.

Try {InputStream inputStream = encodedResource.getResource (). GetInputStream (); try {InputSource inputSource = new InputSource (inputStream); if (encodedResource.getEncoding ()! = null) {inputSource.setEncoding (encodedResource.getEncoding ());} return doLoadBeanDefinitions (inputSource, encodedResource.getResource ()) } finally {inputStream.close ();}}

Here is the second part, the core, which first restores Resource to InputStream, and then calls the actual parsed method doLoadBeanDefinitions (). As you can see, this naming method is worth learning. A business method, such as parse (), may need to do some peripheral work, and then the actual parsing method can be named doParse (). This naming method of doXXX () is used in many open source frameworks, such as logback.

Next, take a look at the doLoadBeanDefinitions () method.

Protected int doLoadBeanDefinitions (InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {try {Document doc = doLoadDocument (inputSource, resource); return registerBeanDefinitions (doc, resource); return registerBeanDefinitions (doc, resource);} 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);}}

Put aside exception handling: the core code is as follows:

Document doc = doLoadDocument (inputSource, resource); return registerBeanDefinitions (doc, resource)

The doLoadDocument method reads the InputStream into a standard Document object, and then calls registerBeanDefinitions () for parsing.

Protected Document doLoadDocument (InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument (inputSource, getEntityResolver (), this.errorHandler, getValidationModeForResource (resource), isNamespaceAware ());}

Next, let's take a look at the core method registerBeanDefinitions.

What public int registerBeanDefinitions (Document doc, Resource resource) throws BeanDefinitionStoreException {/ / creates is actually an instance of DefaultBeanDefinitionDocumentReader, created using reflection. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader (); documentReader.setEnvironment (this.getEnvironment ()); int countBefore = getRegistry (). GetBeanDefinitionCount (); documentReader.registerBeanDefinitions (doc, createReaderContext (resource)); return getRegistry (). GetBeanDefinitionCount ()-countBefore;}

Pay attention to two points here:

1.Document object

First of all, this Document object is a standard XML object defined by W3C and has nothing to do with spring. Secondly, I think the naming of this registerBeanDefinitions method is a little misleading. Because at this time, in fact, the parsing has not yet begun, how to register directly. A better name, I think, could be parseAndRegisterBeanDefinitions ().

2.documentReader is created using reflection, with the following code

Protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader () {return BeanDefinitionDocumentReader.class.cast (BeanUtils. InstantiateClass (this.documentReaderClass);}

A parameter of type Class is passed in the instantiateClass method. Trace back and find the following code:

Private Class documentReaderClass = DefaultBeanDefinitionDocumentReader.class

So the documentReaderClass created is an instance of the DefaultBeanDefinitionDocumentReader class.

Next, let's go to the registerBeanDefinitions () method defined in BeanDefinitionDocumentReader.

Public void registerBeanDefinitions (Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext; logger.debug ("Loading bean definitions"); Element root = doc.getDocumentElement (); doRegisterBeanDefinitions (root);}

After handling the peripheral transaction, enter the doRegisterBeanDefinitions () method, which is a naming convention, which has been introduced above

Protected void doRegisterBeanDefinitions (Element root) {String profileSpec = root.getAttribute (PROFILE_ATTRIBUTE); if (StringUtils.hasText (profileSpec)) {Assert.state (this.environment! = null, "environment property must not be null"); String [] specifiedProfiles = StringUtils.tokenizeToStringArray (profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (! this.environment.acceptsProfiles (specifiedProfiles)) {return }} / / any nested elements will cause recursion in this method. In / / order to propagate and preserve 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 = createHelper (readerContext, root, parent); preProcessXml (root); parseBeanDefinitions (root, this.delegate); postProcessXml (root); this.delegate = parent;}

This method is also relatively long. Take it apart.

String profileSpec = root.getAttribute (PROFILE_ATTRIBUTE); if (StringUtils.hasText (profileSpec)) {Assert.state (this.environment! = null, "environment property must not be null"); String [] specifiedProfiles = StringUtils.tokenizeToStringArray (profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (! this.environment.acceptsProfiles (specifiedProfiles)) {return;}}

If the element in the configuration file is equipped with the profile attribute, it will enter this section, but it will not.

BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper (readerContext, root, parent); preProcessXml (root); parseBeanDefinitions (root, this.delegate); postProcessXml (root); this.delegate = parent

Then the BeanDefinitionParserDelegate object is created, preProcessXml () and postProcessXml () are empty methods, and the core is the parseBeanDefinitions () method. Here, the work of BeanDefinition parsing and registration is delegated to the BeanDefinitionParserDelegate object, which is done in the parseBeanDefinitions () method.

In general, the delegate chain of parsing work is as follows: ClassPathXmlApplicationContext,XmlBeanDefinitionReader,DefaultBeanDefinitionDocumentReader,BeanDefinitionParserDelegate

ClassPathXmlApplicationContext, as the outermost component, initiates a request for resolution

XmlBeanDefinitionReader encapsulates the path of the configuration file as Resource, reads out the Document object defined by W3C, and then delegates it to DefaultBeanDefinitionDocumentReader

DefaultBeanDefinitionDocumentReader starts to do the actual parsing work, but when it comes to the specific parsing of bean, it will continue to delegate to BeanDefinitionParserDelegate.

What happens next in the parseBeanDefinitions () method, and what the BeanDefinitionParserDelegate class does, will be covered in the next blog post.

LoadBeanDefinitions

The parsing of BeanDefinition has reached DefaultBeanDefinitionDocumentR.

In eader, the configuration file has been loaded and parsed into a W3C Document object. This blog goes on to introduce how DefaultBeanDefinitionDocumentReader and BeanDefinitionParserDelegate classes work together to parse and register bean.

BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper (readerContext, root, parent); preProcessXml (root); parseBeanDefinitions (root, this.delegate); postProcessXml (root); this.delegate = parent

This code creates a BeanDefinitionParserDelegate component, followed by the preProcessXml (), parseBeanDefinitions (), postProcessXml () methods

Where preProcessXml () and postProcessXml () are empty methods by default, then take a look at the parseBeanDefinitions () method

Protected void parseBeanDefinitions (Element root, BeanDefinitionParserDelegate delegate) {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)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } 从这个方法开始,BeanDefinitionParserDelegate就开始发挥作用了,判断当前解析元素是否属于默认的命名空间,如果是的话,就调用parseDefaultElement()方法,否则调用delegate上parseCustomElement()方法 public boolean isDefaultNamespace(String namespaceUri) { return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri)); } public boolean isDefaultNamespace(Node node) { return isDefaultNamespace(getNamespaceURI(node)); } 只有** http://www.springframework.org/schema/beans**,会被认为是默认的命名空间。也就是说,beans、bean这些元素,会认为属于默认的命名空间,而像task:scheduled这些,就认为不属于默认命名空间。 根节点beans的一个子节点bean,是属于默认命名空间的,所以会进入parseDefaultElement()方法 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); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } } 这里可能会有4种情况,import、alias、bean、beans,分别有一个方法与之对应,这里解析的是bean元素,所以会进入processBeanDefinition()方法 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. 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)); } } 这里主要有3个步骤,先是委托delegate对bean进行解析,然后委托delegate对bean进行装饰,最后由一个工具类来完成BeanDefinition的注册 可以看出来,DefaultBeanDefinitionDocumentReader不负责任何具体的bean解析,它面向的是xml Document对象,根据其元素的命名空间和名称,起一个类似路由的作用(不过,命名空间的判断,也是委托给delegate来做的)。所以这个类的命名,是比较贴切的,突出了其面向Document的特性。具体的工作,是由BeanDefinitionParserDelegate来完成的 下面就看下parseBeanDefinitionElement()方法 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List aliases = new ArrayList(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() >

BeanClassName.length () & &! this.readerContext.getRegistry () .isBeanNameInUse (beanClassName)) {aliases.add (beanClassName) }} if (logger.isDebugEnabled ()) {logger.debug ("Neither XML 'id' nor' name' specified -" + "using generated bean name [" + beanName + "]") } catch (Exception ex) {error (ex.getMessage (), ele); return null;}} String [] aliasesArray = StringUtils.toStringArray (aliases); return new BeanDefinitionHolder (beanDefinition, beanName, aliasesArray);} return null }

This method is very long and can be divided into three sections.

String id = ele.getAttribute (ID_ATTRIBUTE); String nameAttr = ele.getAttribute (NAME_ATTRIBUTE); List aliases = new ArrayList (); if (StringUtils.hasLength (nameAttr)) {String [] nameArr = StringUtils.tokenizeToStringArray (nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll (Arrays.asList (nameArr));} String beanName = id If (! StringUtils.hasText (beanName) & &! aliases.isEmpty ()) {beanName = aliases.remove (0); if (logger.isDebugEnabled ()) {logger.debug ("No XML 'id' specified-using'" + beanName + "as bean name and" + aliases + "as aliases") } if (containingBean = = null) {checkNameUniqueness (beanName, aliases, ele);}

This paragraph mainly deals with some things related to alias,id and other logos.

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement (ele, beanName, containingBean)

This line is the core, for actual parsing.

If (beanDefinition! = null) {if (! StringUtils.hasText (beanName)) {try {if (containingBean! = null) {beanName = BeanDefinitionReaderUtils.generateBeanName (beanDefinition, this.readerContext.getRegistry (), true) } else {beanName = this.readerContext.generateBeanName (beanDefinition); / / Register an alias for the plain bean class name, if still possible, / / if the generator returned the class name plus a suffix. / / This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName (); if (beanClassName! = null & & beanName.startsWith (beanClassName) & & beanName.length () > beanClassName.length () & &! this.readerContext.getRegistry (). IsBeanNameInUse (beanClassName)) {aliases.add (beanClassName) }} if (logger.isDebugEnabled ()) {logger.debug ("Neither XML 'id' nor' name' specified -" + "using generated bean name [" + beanName + "]") } catch (Exception ex) {error (ex.getMessage (), ele); return null;}} String [] aliasesArray = StringUtils.toStringArray (aliases); return new BeanDefinitionHolder (beanDefinition, beanName, aliasesArray);}

This section is post-processing, dealing with beanName.

Pre-processing and post-processing are not the core, so we won't take a closer look at them. Let's focus on the line called by the core.

Public AbstractBeanDefinition parseBeanDefinitionElement (Element ele, String beanName, BeanDefinition containingBean) {this.parseState.push (new BeanEntry (beanName)); String className = null; if (ele.hasAttribute (CLASS_ATTRIBUTE)) {className = ele.getAttribute (CLASS_ATTRIBUTE). Trim ();} try {String parent = null If (ele.hasAttribute (PARENT_ATTRIBUTE)) {parent = ele.getAttribute (PARENT_ATTRIBUTE);} AbstractBeanDefinition bd = createBeanDefinition (className, parent); parseBeanDefinitionAttributes (ele, beanName, containingBean, bd); bd.setDescription (DomUtils.getChildElementValueByTagName (ele, DESCRIPTION_ELEMENT)); parseMetaElements (ele, bd); parseLookupOverrideSubElements (ele, bd.getMethodOverrides ()) ParseReplacedMethodSubElements (ele, bd.getMethodOverrides ()); parseConstructorArgElements (ele, bd); parsePropertyElements (ele, bd); parseQualifierElements (ele, bd); bd.setResource (this.readerContext.getResource ()); bd.setSource (extractSource (ele)); return bd } catch (ClassNotFoundException ex) {error ("Bean class [" + className + "] not found", ele, ex);} catch (NoClassDefFoundError err) {error ("Class that bean class [" + className + "] depends on not found", ele, err);} catch (Throwable ex) {error ("Unexpected failure during bean definition parsing", ele, ex) } finally {this.parseState.pop ();} return null;}

This method is also quite long. Take a look at it.

This.parseState.push (new BeanEntry (beanName)); String className = null; if (ele.hasAttribute (CLASS_ATTRIBUTE)) {className = ele.getAttribute (CLASS_ATTRIBUTE). Trim ();}

This section extracts the class name from the configuration. For the next long period, put aside the exception handling and take a look at the actual business.

String parent = null; if (ele.hasAttribute (PARENT_ATTRIBUTE)) {parent = ele.getAttribute (PARENT_ATTRIBUTE);} AbstractBeanDefinition bd = createBeanDefinition (className, parent); parseBeanDefinitionAttributes (ele, beanName, containingBean, bd); bd.setDescription (DomUtils.getChildElementValueByTagName (ele, DESCRIPTION_ELEMENT)); parseMetaElements (ele, bd) ParseLookupOverrideSubElements (ele, bd.getMethodOverrides ()); parseReplacedMethodSubElements (ele, bd.getMethodOverrides ()); parseConstructorArgElements (ele, bd); parsePropertyElements (ele, bd); parseQualifierElements (ele, bd); bd.setResource (this.readerContext.getResource ()); bd.setSource (extractSource (ele)); return bd

The naming of each method here shows what it is going to do, and you can follow it one by one. I won't go into details in this article. In short, after parsing here, you get a complete BeanDefinitionHolder. Just to be clear, if some properties are not set in the configuration file, such as autowire-candidate, then the BeanDefinition generated by the parsing will get a default value.

Then, make some necessary decorations for the Bean

Public BeanDefinitionHolder decorateBeanDefinitionIfRequired (Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {BeanDefinitionHolder finalDefinition = definitionHolder; / / Decorate based on custom attributes first. NamedNodeMap attributes = ele.getAttributes (); for (int I = 0; I < attributes.getLength (); iTunes +) {Node node = attributes.item (I); finalDefinition = decorateIfRequired (node, finalDefinition, containingBd);} / / Decorate based on custom nested elements. NodeList children = ele.getChildNodes (); for (int I = 0; I < children.getLength (); iTunes +) {Node node = children.item (I); if (node.getNodeType () = = Node.ELEMENT_NODE) {finalDefinition = decorateIfRequired (node, finalDefinition, containingBd);}} return finalDefinition;}

After continuous single-step debugging, the code continues to run to registerBeanDefinition () in processBeanDefinition in DefaultBeanDefinitionDocumentReader

BeanDefinitionReaderUtils.registerBeanDefinition (bdHolder, getReaderContext (). GetRegistry ())

Step into the code to discover the BeanDefinitionReaderUtils static method registerBeanDefinition ()

Public static void registerBeanDefinition (BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {/ / Register bean definition under primary name. String beanName = definitionHolder.getBeanName (); / / actually calls the registerBeanDefinition method registry.registerBeanDefinition (beanName, definitionHolder.getBeanDefinition ()) in DefaultListableBeanFactory; / / Register aliases for bean name, if any. String [] aliases = definitionHolder.getAliases (); if (aliases! = null) {for (String aliase: aliases) {registry.registerAlias (beanName, aliase);}

Explain that the registerBeanDefinition method in DefaultListableBeanFactory is actually called, because DefaultListableBeanFactory implements the BeanDefinitionRegistry interface, and the registerBeanDefinition () method is defined in the BeanDefinitionRegistry interface.

Take a look at the specific implementation of the registerBeanDefinition () instance method in DefaultListableBeanFactory:

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);}} synchronized (this.beanDefinitionMap) {Object oldBeanDefinition = this.beanDefinitionMap.get (beanName) If (oldBeanDefinition! = null) {if (! this.allowBeanDefinitionOverriding) {throw new BeanDefinitionStoreException (beanDefinition.getResourceDescription (), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean'" + beanName + "': There is already [" + oldBeanDefinition + "] bound.") } else {if (this.logger.isInfoEnabled ()) {this.logger.info ("Overriding bean definition for bean'" + beanName + "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]") } else {this.beanDefinitionNames.add (beanName); this.frozenBeanDefinitionNames = null;} this.beanDefinitionMap.put (beanName, beanDefinition); resetBeanDefinition (beanName);}}

After the code trace, it is found that the most important thing in this method is the following two lines:

This.beanDefinitionNames.add (beanName); this.beanDefinitionMap.put (beanName, beanDefinition)

The former puts the beanName in the queue, the latter puts the BeanDefinition in the map, and the registration is completed. When instantiating later, the BeanDefinition in beanDefinitionMap is taken out and instantiated one by one

After the BeanFactory is ready, the code goes back to ClassPathXmlApplicationContext

Public void refresh () throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {/ / Prepare this context for refreshing. PrepareRefresh (); / / Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory (); / / Prepare the bean factory for use in this context. 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) {/ / Destroy already created singletons to avoid dangling resources. DestroyBeans (); / / Reset 'active' flag. CancelRefresh (ex); / / Propagate exception to caller. Throw ex;}

That is, after the obtainFreshBeanFactory () method is executed, proceed to the following steps.

To sum up, ApplicationContext delegates the work of parsing the configuration file to BeanDefinitionReader, and then BeanDefinitionReader reads the configuration file as a Document document of xml, and then delegates it to BeanDefinitionDocumentReader

The BeanDefinitionDocumentReader component acts as a route based on the namespace and element name of the xml element. The actual parsing work is delegated to BeanDefinitionParserDelegate.

After the parsing of BeanDefinitionParserDelegate is completed, BeanDefinitionHolder will be returned to BeanDefinitionDocumentReader, where DefaultListableBeanFactory will be delegated to complete the registration of bean.

XmlBeanDefinitionReader (counting, parsing XML documents), BeanDefinitionDocumentReader (relying on xml documents for parsing and registration), BeanDefinitionParserDelegate (actual parsing work). It can be seen that in the process of parsing bean, the division of labor of these three components is relatively clear and each has its own responsibility. This kind of design idea is worth learning.

This is the end of the content of "what is the loading process of the Spring IOC container". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report