This article examines the process of parsing Spring configuration files to registration.

Let’s write a demo to debug

 XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("iocbeans.xml"));
Copy the code

The first step is to get the XML resource.

The second step is to load the resource.

public XmlBeanFactory(Resource resource) throws BeansException { this(resource, (BeanFactory)null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); / / here is the real implementation of resource loading this. Reader. LoadBeanDefinitions (resource); } public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return this.loadBeanDefinitions(new EncodedResource(resource)); } public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { //... Omit part of the code / / through property records is currently loaded resource Set < EncodedResource > currentResources = (Set) enclosing resourcesCurrentlyBeingLoaded. The get (); if(currentResources == null) { currentResources = new HashSet(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if(! ((Set)currentResources).add(encodedResource)) { throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!" ); } else { int var5; Try {/ / to get an inputStream inputStream inputStream. = encodedResource getResource () getInputStream (); try { InputSource inputSource = new InputSource(inputStream); if(encodedResource.getEncoding() ! = null) { inputSource.setEncoding(encodedResource.getEncoding()); } / / core logic part var5 = this. DoLoadBeanDefinitions (inputSource, encodedResource getResource ()); } finally {// Close the stream inputstream.close (); } } catch (IOException var15) { throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15); } finally { ((Set)currentResources).remove(encodedResource); if(((Set)currentResources).isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } return var5; }}Copy the code

To summarize the logic of the above code:

The incoming Resource parameter is wrapped first because of possible encoding. Then prepare the inputSource object for later reading. Finally, the doLoadBeanDefinitions method was introduced.

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //1. Document doc = this.doloadDocument (inputSource, Resource); / / 2. According to the return doc, registered bean information to return this. RegisterBeanDefinitions (doc, resource); } catch (BeanDefinitionStoreException var4) { throw var4; }Copy the code

Validation of XML is covered in 1 above, which is skipped here.

Parse and register BeanDefinitions

We’ve got the Document (doc, Resource). We’re going to see registerBeanDefinitions(Doc, Resource).

public int registerBeanDefinitions(Document doc, Resource resource) { BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); Int countBefore = this.getregistry ().getBeandefinitionCount (); / / load and registered bean documentReader. RegisterBeanDefinitions (doc, enclosing createReaderContext (resource)); Return this.getregistry ().getBeandefinitionCount () -countbefore; }Copy the code

The doc parameter is converted from the loadDocument loading in the previous section. In this method a good application of the single responsibility principle of the object-oriented logic of handling the entrusted to a single class, and the logical processing class is BeanDefinitionDocumentReader. BeanDefinitionDocumentReader is an interface and the instantiation of the work is in createBeanDefinitionDocumentReader (), BeanDefinitionDocumentReader really by this method is of type DefaultBeanDefinitionDocumentReader, after entering, find that one of the important purpose of this method is to extract the root, This makes it easier to continue BeanDefiniton registration again with root as an argument.

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; this.logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); this.doRegisterBeanDefinitions(root); {} protected void doRegisterBeanDefinitions Element (root) / / handle parsing BeanDefinitionParserDelegate parent = this. Delegate;  this.delegate = this.createDelegate(this.getReaderContext(), root, parent); if(this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute("profile"); if(StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; "); if(! this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = this.createDelegate(this.getReaderContext(), root, parent); if(this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute("profile"); if(StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; "); if(! this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; }}} // Handle the parsing before leaving it to subclasses to implement this.preprocessxml (root); this.parseBeanDefinitions(root, this.delegate); // This. PostProcessXml (root); this.delegate = parent; }}Copy the code

Entering the this.preprocessxml (root) and this.postprocessxml (root) methods reveals that they are empty methods. What’s the use of an empty method? We can quickly reflect that this is the design pattern of the template. A class is either designed for inheritance or final. These two methods are designed for subclasses.

Now it’s time to read the XML.

void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {/ / to read beans 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)) {/ / handling of bean enclosing parseDefaultElement (ele, delegate); } else {/ / handling of beans delegate parseCustomElement (ele); } } } } else { delegate.parseCustomElement(root); }}Copy the code

Default label resolution

The parsing of default tags is handled in the parseDefaultElement function, where the functionality is clear. Four different labels are treated differently.

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if(delegate.nodeNameEquals(ele, "import")) { this.importBeanDefinitionResource(ele); } else if(delegate.nodeNameEquals(ele, "alias")) { this.processAliasRegistration(ele); } else if(delegate.nodeNameEquals(ele, "bean")) { this.processBeanDefinition(ele, delegate); } else if(delegate.nodeNameEquals(ele, "beans")) { this.doRegisterBeanDefinitions(ele); }}Copy the code

Bean label parsing and registration

Of the above four types of tag parsing, bean tag parsing is the most important and complex, we understand her processing, the other tag parsing will take care of itself. Let’s go into the code and see

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //1. Delegate element parsing to this method. Through this method, various properties of the configuration file are owned in bdHolder. (class,name,id,alias) BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if(bdHolder ! = null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); Try {/ / to the resolved bdHolder register BeanDefinitionReaderUtils. RegisterBeanDefinition (bdHolder, this.getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException var5) { this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5); } // Issue a response event to notify the relevant listener, This.getreadercontext ().FireComponentRegistered (New BeanComponentDefinition(bdHolder)); }}Copy the code

Parsing BeanDefinition

First of all, we from the beginning of the element analysis and information extraction, BeanDefinitionHolder bdHolder = delegate. ParseBeanDefinitionElement (ele), Enter BeanDefinitionParserDelegate parseBeanDefinitionElement method of a class.

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return this.parseBeanDefinitionElement(ele, (BeanDefinition)null); } public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute("id"); String nameAttr = ele.getAttribute("name"); String nameAttr = ele.getAttribute("name"); List<String> aliases = new ArrayList(); if(StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; "); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if(! StringUtils.hasText(id) && ! aliases.isEmpty()) { beanName = (String)aliases.remove(0); } if(containingBean == null) { this.checkNameUniqueness(beanName, aliases, ele); } / / to label the other attributes of the parse AbstractBeanDefinition beanDefinition = this. 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); String beanClassName = beanDefinition.getBeanClassName(); if(beanClassName ! = null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && ! this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } } catch (Exception var9) { this.error(var9.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } else { return null; } } public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; If (ele.hasAttribute("class")) {className = ele.getAttribute("class").trim(); } try {// parse parent attribute String parent = null; if(ele.hasAttribute("parent")) { parent = ele.getAttribute("parent"); } / / create for hosting properties GenericBeanDefinition AbstractBeanDefinition bd = this. CreateBeanDefinition (className, parent); / / hard code parsing the default bean various attributes of the enclosing parseBeanDefinitionAttributes (ele, beanName containingBean, bd); / / extraction description bd. SetDescription (DomUtils getChildElementValueByTagName (ele, "description")); This. parseMetaElements(ele, bd); / / analytical lookup - method enclosing parseLookupOverrideSubElements (ele, bd. GetMethodOverrides ()); / / analytical replaced - method enclosing parseReplacedMethodSubElements (ele, bd. GetMethodOverrides ()); / / parsing constructor enclosing parseConstructorArgElements (ele, bd); / / parsing proterty sub-elements enclosing parsePropertyElements (ele, bd); / / parse the qualifier sub-elements enclosing parseQualifierElements (ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(this.extractSource(ele)); AbstractBeanDefinition var7 = bd; return var7; } catch (ClassNotFoundException var13) { } finally { this.parseState.pop(); } return null; }Copy the code

Finally, we see how all the attributes of the bean tag are resolved.

  1. BeanDefinition BeanDefiniton is an interface that exists in Spring in three implementations: RootBeanDefiniton, ChildBeanDefinitio, and GenericBeanDefinition. All three implementations inherit AbstractBeanDefinition, where BeanDefiniton is the container representation of the configuration file element tag. They correspond to each other. Among them, RootBeanDefinition is the most commonly used implementation class, which corresponds to the general element tag, and GenericBeanDefinition is the newly added bean file configuration attribute definition class, which is a one-stop service class. In a configuration file, you can define parents and children, with the parent being RootBeanDefiniton, the child being childBeanDefinition, and those without a parent being RootBeanDefiniton. Spring translates configuration file information into an internal representation of the container through BeanDefinitions, which are registered with the BeanDefinitionRegistry. BeanDefinitionRegistry is spring’s in-memory database. It is mainly stored as a map. Later, configuration information can be obtained directly from BeanDefinitionRegistry. As you can see, to parse a property, you first create a real example to host the property, that is, create an instance of the GenericBeanDefinition type. CreateBeanDefinition (className, parent) does just that. public static AbstractBeanDefinition createBeanDefinition(String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setParentName(parentName); if(className ! = null) { if(classLoader ! = null) {// If the classLoader is not null, the class object is loaded. Otherwise just record className bd.setBeanClass(classutils.forname (className, classLoader)); } else { bd.setBeanClassName(className); }}

            return bd;
        }
    Copy the code

    Parsing the various properties After we have created a hosted instance of the bean information, we are ready to parse the various properties of the bean information. ParseBeanDefinitionAttributes is element of all elements attributes parsing: public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd) {// Parse scope attribute if(el.hasattribute (“singleton”)) {this.error(“Old 1.x ‘singleton’ attribute in use – upgrade to ‘scope’ declaration”, ele); } else if(ele.hasAttribute(“scope”)) { bd.setScope(ele.getAttribute(“scope”)); } else if(containingBean ! = null) { bd.setScope(containingBean.getScope()); } // Parse the abstract attribute if(el.hasAttribute (“abstract”)) {bd.setabstract (“true”.equals(el.getAttribute (“abstract”))); } // Parse the lazy-init attribute String lazyInit = el.getAttribute (“lazy-init”); if(“default”.equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); }

    bd.setLazyInit("true".equals(lazyInit)); // Parse the autowire attribute String Autowire = ele.getAttribute("autowire"); bd.setAutowireMode(this.getAutowireMode(autowire)); DependencyCheck = ele. GetAttribute ("dependency-check"); bd.setDependencyCheck(this.getDependencyCheck(dependencyCheck)); String autowireCandidate; // Resolve the validity attribute if(el.hasattribute ("depends ")) {autowireCandidate = el.getAttribute ("depends "); bd.setDependsOn(StringUtils.tokenizeToStringArray(autowireCandidate, ",; ")); } // autowireCandidate = el.getAttribute ("autowire-candidate"); String destroyMethodName; if(!" ".equals(autowireCandidate) && !" default".equals(autowireCandidate)) { bd.setAutowireCandidate("true".equals(autowireCandidate)); } else { destroyMethodName = this.defaults.getAutowireCandidates(); if(destroyMethodName ! = null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(destroyMethodName); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); If (el.hasAttribute ("primary")) {bd.setPrimary("true".equals(el.getAttribute ("primary"))); } // Parsing the init-method attribute if(el.hasattribute ("init-method")) {destroyMethodName = el.getAttribute ("init-method"); if(!" ".equals(destroyMethodName)) { bd.setInitMethodName(destroyMethodName); } } else if(this.defaults.getInitMethod() ! = null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } // Resolve the destroy-method attribute if(el.hasattribute ("destroy-method")) {destroyMethodName = el.getAttribute ("destroy-method"); bd.setDestroyMethodName(destroyMethodName); } else if(this.defaults.getDestroyMethod() ! = null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); If (el.hasattribute ("factory-method")) { bd.setFactoryMethodName(ele.getAttribute("factory-method")); } // Parse the factory-bean attribute if(ele.hasattribute ("factory-bean")) {bd.setFactoryBeanname (ele.getAttribute("factory-bean")); } return bd; }Copy the code

After the above processing, Spring completes parsing all bean properties. That is, all configurations in XML can be found in the instance class of GenericBeanDefinition. GenericBeanDefinition is just a subclass implementation, and most general properties are stored in AbstractBeanDefinition. Let’s take a look at this class

/ /... Private Volatile Object beanClass; private String scope; private boolean abstractFlag; private boolean lazyInit; private int autowireMode; private int dependencyCheck; private String[] dependsOn; private boolean autowireCandidate; private boolean primary; private final Map<String, AutowireCandidateQualifier> qualifiers; private boolean nonPublicAccessAllowed; private boolean lenientConstructorResolution; private ConstructorArgumentValues constructorArgumentValues; private MutablePropertyValues propertyValues; private MethodOverrides methodOverrides; private String factoryBeanName; private String factoryMethodName; private String initMethodName; private String destroyMethodName; private boolean enforceInitMethod; private boolean enforceDestroyMethod; private boolean synthetic; private int role; private String description; private Resource resource;Copy the code

Register resolved BeanDefinitions

Once we’ve parsed the configuration file, the resulting beanDefinition is ready for future use, and the only thing left to do is register. We go into

#DefaultBeanDefinitionDocumentReader BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry()); public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {/ / using beanName do register a unique identifier String beanName = definitionHolder. GetBeanName (); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); / / register all aliases String [] aliases. = definitionHolder getAliases (); if(aliases ! = null) { String[] var4 = aliases; int var5 = aliases.length; for(int var6 = 0; var6 < var5; ++var6) { String alias = var4[var6]; registry.registerAlias(beanName, alias); }}}Copy the code

As you can see from the above code, all resolved BeanDefinitions are registered inRegistry of type BeanDefinitioinRegistry, and registration of BeanDefinitions is divided into two parts :beanName registration and alias registration.

  1. Register a BeanDefinition with a beanName and the registration of a BeanDefinition is essentially a beanName as a key and put it into a map. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {

    If (beanDefinition instanceof AbstractBeanDefinition) {try {// Last check ((AbstractBeanDefinition)beanDefinition).validate(); } catch (BeanDefinitionValidationException var9) } } BeanDefinition oldBeanDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName); if(oldBeanDefinition ! = null) { //... Omit If beanName exists and overwriting is not allowed, throw the exception if(! this.isAllowBeanDefinitionOverriding()) { } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if(this.hasBeanCreationStarted()) { Map var4 = this.beanDefinitionMap; / / lock into the map synchronized (enclosing beanDefinitionMap) {this. BeanDefinitionMap. Put (beanDefinition beanName); 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 { this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); } this.frozenBeanDefinitionNames = null; } if(oldBeanDefinition ! = null || this.containsSingleton(beanName)) { this.resetBeanDefinition(beanName); // Reset all beanName caches}}Copy the code
    1. BeanDefinition public void registerAlias(String name, String alias) {// If beanName is the same as alias, delete the corresponding alias if(alias.equals(name)) {this.aliasmap.remove (alias); } else { String registeredName = (String)this.aliasMap.get(alias); if(registeredName ! = null) { if(registeredName.equals(name)) { return; }

      if(! this.allowAliasOverriding()) { } this.checkForAliasCircle(name, alias); // Register this.aliasmap. put(alias, name); }}Copy the code