It all starts with kneeling, silver goalkeeper
Now all the articles about spring will be reorganized later. Here is just a record. Until I'm familiar with ioc, I'll string up the previous articles.
Parsing xml tags starts with the parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) method in the DefaultBeanDefinitionDocumentReader class. The details are as follows:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // import tag if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias tag else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean tag else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { /** * Focus on the parsing of bean Tags */ processBeanDefinition(ele, delegate); } // beans label else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
We can see that it is to process the four tags of import, alias, bean and beans. We will focus on the parsing of this bean tag, and the rest should be solved. The code is as follows:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { /** * Extract the information from the < bean / > node and encapsulate it into a bean definition holder * Property bean Property definition in * class Fully qualified name of class * name id and name can be specified (separated by comma, semicolon and space) * scope Scope * constructor arguments Specify construction parameters * properties Set the value of the property * autowiring mode no(Default), byName, byType, constructor * lazy-initialization mode Whether it is lazy to load (if it is dependent on non lazy bean s, it cannot be lazy to load) * initialization method bean When the property is set, this method is called * destruction method bean Callback method after destruction * *Here is the definition of a bean * <bean id="exampleBean" name="name1, name2, name3" class="com.javadoop.ExampleBean" * scope="singleton" lazy-init="true" init-method="init" destroy-method="cleanup"> * * <!-- You can specify construction parameters in the following three forms -- > * <constructor-arg type="int" value="7500000"/> * <constructor-arg name="years" value="7500000"/> * <constructor-arg index="0" value="7500000"/> * * <!-- property Several cases of -- > * <property name="beanOne"> * <ref bean="anotherExampleBean"/> * </property> * <property name="beanTwo" ref="yetAnotherBean"/> * <property name="integerProperty" value="1"/> * </bean> * * bdHolder The instance already contains class name id alias and other class attributes */ BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { /** * If there is a custom attribute, it should be resolved accordingly */ bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. /** * This step registers the resolved bdHolder */ BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. /** * After registration, send the event. Skip here */ getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
The logic is as follows:
- Delegate the parseBeanDefinitionElement() method in BeanDefinitionParserDelegate for element resolution.
- If the returned bdHolder is not empty, if there are custom attributes under the child node of the default tag, the custom tag needs to be resolved.
- After parsing, register the resolved bdHolder.
- Issue a response event to notify the relevant listener.
The parseBeanDefinitionElement() method is parsed as follows:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); /** * Divide the definition of the name attribute into commas, semicolons, and spaces to form an array of alias lists, * Of course, if you don't define it, it's empty */ List<String> 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 no id is specified, use the first name of the alias list as the beanName */ if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } /** * Here is the configuration information in < bean >... < bean / > */ AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); /** * At this point, the whole < bean / > tag is parsed and a bean definition is formed */ if (beanDefinition != null) { /** * If both id and name are not set, then the beanName will be null at this time. Enter the following code to generate */ if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { /** * bean Name generation method */ 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.isTraceEnabled()) { logger.trace("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; }
The process is as follows:
- Extract the id and name attributes in the element
- Further parsing other properties
- If it is detected that the bean does not specify a beanName, use the default rule to generate a beanName for this bean
- Encapsulate the acquired information into an instance of BeanDefinitionHolder
The parseBeanDefinitionElement(ele, beanName, containingBean) method in this class is parsed as follows:
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { /** * Create BeanDefinition and set class information */ AbstractBeanDefinition bd = createBeanDefinition(className, parent); /** * Set a bunch of properties of BeanDefinition, which are defined in AbstractBeanDefinition */ parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); /** * The following pile is to parse the inner child elements of < bean >... < bean >, * After parsing, all the information will be put into the bd attribute */ /** * Analysis < meta / > */ parseMetaElements(ele, bd); /** * Analysis < lookup method / > */ parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); /** * Parse < replaced method / > */ parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); /** * Parse < constructor Arg / > */ parseConstructorArgElements(ele, bd); /** * Parse < property / > */ parsePropertyElements(ele, bd); /** * Analyze < qualifier / > */ 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; }
From this method, we can basically see all the tags of the bean.
So far, we found that the previous bean definition uses genericbean definition, which is a bean file configuration property definition class added after version 2.5, and is a one-stop service class. If you are interested, you can see how the corresponding tags are parsed. I am too lazy to read them. At this point, the default label resolution is complete. Here's how delegate.decorateBeanDefinitionIfRequired(ele, bdHolder) method, parse the custom tag elements in the default tag, I don't think it's what I need, skip, go to the following important method bean D efinitionReaderUtils.registerBeanDefinition (bdHolder, getreadercontext(). Getregistry()), register the resolved bdHolder as follows:
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); /** * Key methods! */ 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); } } }
There is nothing to say. Go straight to the key methods:
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 { /** * The last verification before registration is different from the previous xml file verification * It mainly checks the methodOverrides in the AbstractBeanDefinition attribute * Verify that methodOverrides and factory methods coexist or that the method corresponding to methodOverrides does not exist at all */ ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { /** *If it is not allowed to overwrite, throw the exception directly */ if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } /** * Using framework beans to override user-defined beans */ 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 + "]"); } } /** * Overwrite old bean s with new ones */ 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 { /** * Overwrite old beans with equivalent beans */ if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } /** * cover */ this.beanDefinitionMap.put(beanName, beanDefinition); } else { /** * Determine whether other beans have started to initialize * Note that the "register Bean" action is over, and the Bean is still not initialized * At the end of Spring container startup, all singleton beans will be pre initialized */ 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; removeManualSingletonName(beanName); } } else { /** * The most normal will come here * Put banddefinition into a map, which saves all beandefinitions */ // Still in startup registration phase this.beanDefinitionMap.put(beanName, beanDefinition); /** * This ArrayList will save the names of each registered bean in the order of configuration */ this.beanDefinitionNames.add(beanName); /** * Manually injected beans are implemented by calling the registerSingleton(String beanName, Object singletonObject) method */ removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; } if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }
The process is as follows:
- Validation of AbstractBeanDefinition. The validation here is for the methodOverrides property of AbstractBeanDefinition.
- The processing of the registered beanName. If you set not to allow bean overrides, you need to throw an exception, otherwise you can override directly.
- Add to map cache.
- Clear the cache corresponding to beanName left before parsing.
You don't need to read the following alias registration. Let's say something about it:
- Alias is the same as beanName. If alias and beanName have the same name, you do not need to process and delete the original alias
- alias overlay processing. If aliasName has been used and pointed to another beanName, it needs to be processed by user settings
- alias cycle check. When a - > b exists, an exception will be thrown if a - > C - > b occurs again.
- Register alias.
The final step is to notify the listener that the parsing and registration are complete.
Reference: deep analysis of spring (version 2) source code