Antecedents to review
Last time, “Spring IoC Container Initialization (2),” we talked about how Spring parsed the
tag we defined. The code went through layer after layer, Finally came to the BeanDefinitionParserDelegate# parseBeanDefinitionElement method. However, this method is superficial and does not delve into parsing properties like class and child tags like property in
.
This article continues.
Well, be patient and write your own demo to follow the interruption points, so that you can understand more deeply.
How do you parse the contents of <bean>?
Moving on to the code:
public class BeanDefinitionParserDelegate {
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
String className = null;
// Read the class attribute of the
tag
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
// Read the parent property of the
tag
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// GenericBeanDefinition object
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// Parse scope, lazy-init, autowire, etc
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// Parse meta tags
parseMetaElements(ele, bd);
// parse the lookup-method tag
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// Parse the replace-method tag
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// Parse the constructor-arg tag
parseConstructorArgElements(ele, bd);
// Parse the property tag
parsePropertyElements(ele, bd);
// Resolve the Qualifier tag
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
// catch ...
return null; }}Copy the code
This is where you actually parse the contents of <bean> tags, such as the usual class, parent, scope, lazy-init, Autowire, property, constructor-arg, etc. There are also unusual lookup-method, replace-method, and so on.
The method internally calls one method after another to parse different tags. Here we only follow up the common property analysis, other methods are generally similar, you can study by yourself if you are interested.
The parsePropertyElements method code is as follows:
public class BeanDefinitionParserDelegate {
// Parse the property tag
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// Filter the
tag
if(isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); }}}public void parsePropertyElement(Element ele, BeanDefinition bd) {
// property Specifies the name attribute of the label
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if(! StringUtils.hasLength(propertyName)) {// error
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
if (bd.getPropertyValues().contains(propertyName)) {
// error
return;
}
// This resolves to a RuntimeBeanReference or TypedStringValue
Object val = parsePropertyValue(ele, bd, propertyName);
PropertyValue pv = new PropertyValue(propertyName, val);
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
// Add the parsed value to BeanDefinition's property list
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop(); }}}Copy the code
What does this method basically do?
- Walk through the node and find the property label
- Parse the name attribute of the property tag and encapsulate its corresponding value as either RuntimeBeanReference or TypedStringValue (where the former corresponds to the ref attribute and the latter to the value attribute, See the application-IOC.xml file above), then encapsulate it as PropertyValue and save it to BeanDefinition’s property list.
The process for resolving ref and value is as follows:
public class BeanDefinitionParserDelegate {
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName ! =null ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element");
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceofElement && ! nodeNameEquals(node, DESCRIPTION_ELEMENT) && ! nodeNameEquals(node, META_ELEMENT)) {// Child element is what we're looking for.
if(subElement ! =null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else{ subElement = (Element) node; }}}// The ref and value attributes cannot coexist
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement ! =null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
/ / ref attribute
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if(! StringUtils.hasText(refName)) { error(elementName +" contains empty 'ref' attribute", ele);
}
// Encapsulate as RuntimeBeanReference
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
/ / the value attribute
else if (hasValueAttribute) {
// Encapsulate as TypedStringValue
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
// If there are child elements, continue parsing
else if(subElement ! =null) {
// This contains the child tags of the property tag, such as list, map, set, etc
return parsePropertySubElement(subElement, bd);
}
else {
error(elementName + " must specify a ref or value", ele);
return null; }}}Copy the code
Property tags are relatively complex to parse, and other tags (meta, constructor-arg, etc.) are generally similar.
After BeanDefinitionParserDelegate# parseBeanDefinitionElement method after parsing and encapsulation, have saved our custom BeanDefinition bean information, GenericBeanDefinition namely. Spring also wraps BeanDefinitionHolder and alias information as BeanDefinitionHolder:
public class BeanDefinitionParserDelegate {
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// ...
// Parse the BeanDefinition
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if(beanDefinition ! =null) {
// ...
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null; }}Copy the code
In addition, in registered before with the IoC container, there is also a decorateBeanDefinitionIfRequired method, it is mainly used to deal with a default namespace (www.springframework.org/schema/bean.) Other bean definitions, such as
,
, etc., will continue to follow the main line here without further analysis.
The next step is to register the BeanDefinition with the IoC container:
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
// ...
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// BeanDefinitionHolder encapsulated as BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if(bdHolder ! =null) {
// Process beans that are not in the default namespace
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
/ / register BeanDefinition
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(newBeanComponentDefinition(bdHolder)); }}}Copy the code
public abstract class BeanDefinitionReaderUtils {
// ...
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
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); }}}}Copy the code
What is the IoC container? How do I register?
Above mentioned, the Spring IoC container by default is DefaultListableBeanFactory, look at its inheritance structure:
You can see DefaultListableBeanFactory BeanDefinitionRegistry interface is realized.
The so-called “registered” to the IoC container, is actually storing BeanDefinition DefaultListableBeanFactory holding a Map, as follows:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory.BeanDefinitionRegistry.Serializable {
// ...
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); }}// Get an existing BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if(existingDefinition ! =null) {
if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
// Do these exceptions look familiar?
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 + "]"); }}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; removeManualSingletonName(beanName); }}else {
// Register to Map
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if(existingDefinition ! =null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if(isConfigurationFrozen()) { clearByTypeCache(); }}}Copy the code
Do the above anomalies look familiar?
What is this beanDefinitionMap? It is a Map:
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
Copy the code
summary
At this point, Spring has read and parsed the <bean> tag information from the application-IOC. XML file we defined and converted it into an internal data structure, BeanDefinition, Then register to the IoC container (i.e. DefaultListableBeanFactory).
In order to have an overall grasp, the main process is organized into a mind map:
In fact, the previous several articles are mainly the first step, that is, “initialize BeanFactory, register Bean definition”, and only follow a main line down, other details interested partners can study by themselves.
The IoC container has been created, and the BeanDefinition has been placed init. How do we get the objects we want from the container?
To find out what happens next, listen next time