Previous posts:
1. Deep understanding of Spring IOC(I), unified resource loading
In the last article, we looked at Spring’s uniform Resource loading strategy, and we learned that XML must be loaded as Resource using ResourceLoader before it can be used. However, ResourceLoader can only load one file as Resource at a time, so when you need to load multiple resources at the same time, you need to use ResourcePatternReslover. So what happens next, after you load the XML as a Resource, what happens to the information in the XML after you parse it? This article will answer that question.
BeanDefinition
This is one thing we have to say before we talk about parsing XML. Let’s look at the description of this interface in the source code:
Why do you have to say this? Because BeanDefinition is the basis for you to really read the source code, a lot of the extensions are based on it, and a lot of the core flow requires you to understand it. Many people claim to have read the Spring source code, but end up not knowing even the most basic BeanDefinition.Copy the code
BeanDefinition describes the attributes of an instance of a bean, its constructor parameters, and more information supported by subclasses. Let’s look at the inheritance of the BeanDefinition interface:
Priority is AbstractBeanDefinition this abstract class, it has a default implementation for most BeanDefinition method, and a lot of properties, these properties determine the Spring how to instantiate the corresponding bean. AbstractBeanDefinition class AbstractBeanDefinition class AbstractBeanDefinition class AbstractBeanDefinition class AbstractBeanDefinition
There are a lot of attributes. You don't need to remember them. You can read most of them firstCopy the code
The code block1
// The Java class corresponding to the bean
@Nullable
private volatile Object beanClass;
// The scope of the bean, corresponding to the scope property
@Nullable
private String scope = SCOPE_DEFAULT;
// Whether the class is abstract
private boolean abstractFlag = false;
// Whether to load lazily
private boolean lazyInit = false;
// Automatic injection
private int autowireMode = AUTOWIRE_NO;
// Dependency check
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
DependenOn specifies that an instantiation of a bean depends on another bean being instantiated first. This attribute is dependent on the @dependenon attribute
@Nullable
private String[] dependsOn;
// The autowire-candidate property is set to false so that the bean is not considered by the container when it looks for an autowier-candidate, that is, it is not considered as a candidate for other bean autowier-candidate,
// However, the bean itself can be used to inject other beans using autowiring
private boolean autowireCandidate = true;
// When multiple bean candidates appear during autowiring, they will be preferred, corresponding to the bean property primary, with the @primary annotation
private boolean primary = false;
// Records the Qualifier, corresponding to the child element Qualifier
private final Map<String, AutowireCandidateQualifier> qualifiers = newLinkedHashMap<>(); @Nullable private Supplier<? > instanceSupplier; private boolean nonPublicAccessAllowed =true;
// Whether to parse the constructor in a loose mode. Default is true
// If you don't understand, skip ahead. We will cover this later
private boolean lenientConstructorResolution = true;
// Corresponds to the bean label attribute factory-bean
@Nullable
private String factoryBeanName;
// Corresponds to the bean label attribute factory-method
@Nullable
private String factoryMethodName;
// Record the constructor injection property corresponding to the bean property constructor-arg
@Nullable
private ConstructorArgumentValues constructorArgumentValues;
// Set of common attributes
@Nullable
private MutablePropertyValues propertyValues;
// The holder of the method override, recording the lookup-method and appet-method elements
@Nullable
private MethodOverrides methodOverrides;
// Initialize the method corresponding to the bean property init-method
@Nullable
private String initMethodName;
// Destroy method corresponding to bean property destroy-method
@Nullable
private String destroyMethodName;
// Whether to execute init-method, program Settings
private boolean enforceInitMethod = true;
// Whether to destroy-method, program Settings
private boolean enforceDestroyMethod = true;
// If AOP is defined by the user rather than the application itself, set to true when AOP is created
private boolean synthetic = false;
// Define the APPLICATION of this bean, APPLICATION: user, INFRASTRUCTURE: completely internal use, independent of user, SUPPORT: part of some complex configuration
// Program Settings
private int role = BeanDefinition.ROLE_APPLICATION;
// Describes the bean
@Nullable
private String description;
// Resource defined by this bean
@Nullable
private Resource resource;
Copy the code
I’ve just listed most of the attributes for the sake of less code. As you can see, there are a lot of attributes, we can almost look at XML and see what it does, and there are a lot of attributes that we’re not familiar with, so don’t worry, just take a look at them, get familiar with them, and I’ll come back to them later if we need to. It is not necessary to remember all of the above attributes, but to remember that the contents of each bean tag in XML will eventually be parsed into a subclass of this BeanDefinition.
BeanDefinitionReader
BeanDefinitionReader (BeanDefinitionReader) {BeanDefinitionReader (BeanDefinitionReader);
The code block2
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
ResourceLoader getResourceLoader();
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
// Core four methods
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String. locations) throws BeanDefinitionStoreException; }Copy the code
The four loadBeanDefinitions methods take different parameters to load a resource, and then return the number of loaded resources. So it’s also easy to understand that the core role of the implementation class of this interface is to load and parse beanDefinitions based on the resource or location of the resource. Let’s look at the inheritance of this interface:
We don’t pay attention to EnvironmentCapable interface first, then looked down from the top, had seen the AbstractBeanDefinitionReader first, and other system routines are similar, this class provides BeanDefinitionReader many default implementation method, Let’s look at its implementation of loadBeanDefinitions:
The code block3
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// An unimplemented method was called
counter += loadBeanDefinitions(resource);
}
return counter;
}
@Override
public int loadBeanDefinitions(String. locations) throws BeanDefinitionStoreException { Assert.notNull(locations,"Location array must not be null");
int counter = 0;
for (String location : locations) {
// The following method is called
counter += loadBeanDefinitions(location);
}
return counter;
}
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
// The following method is called
return loadBeanDefinitions(location, null);
}
// This is an overloaded method
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
If else, use ResourcePatternResolver if the class's resourceLoader attribute is a ResourcePatternResolver instance
// Load resources in batches and then parse them, otherwise load individual resources with normal ResourceLoader
if (resourceLoader instanceof ResourcePatternResolver) {
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 {
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 + "]");
}
returnloadCount; }}Copy the code
Don’t be scared by such a long code, this code belongs to the paper tiger, it is easy to understand carefully. We can easily see that AbstractBeanDefinitionReader does not implement the loadBeanDefinitions BeanDefinitionReader (the Resource the Resource) this method, Instead, it implemented three other methods and added an overloaded method. However, all other implementations end up calling the unimplemented method (and the overloaded method is also called), so we can see that parsing a specific resource, Again, we have to use loadBeanDefinitions (Resource Resource), You need to see us this key is a subclass of AbstractBeanDefinitionReader XmlBeanDefinitionReader (as for why choose this, because we are so far, that’s all is based on the XML configuration). For XmlBeanDefinitionReader, see loadBeanDefinitions (Resource Resource).
The code block4
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
Copy the code
LoadBeanDefinitions (EncodeResource EncodeResource) loadBeanDefinitions (EncodeResource EncodeResource)
The code block5
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// The parameter cannot be null validation
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
// 1. Check whether one XML and another XML refer to each other through import tags
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 2
// 1 = 1; // 2 = 1; // 2 = 2
if(! currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// The key is to translate Resource into InputSource and deliver it to the real doLoadBeanDefinitions.
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(); }}}Copy the code
The most important thing in the code above, which is actually step 3, is to look at the doLoadBeanDefinitions method:
The code block6
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// Get the Document object (Document stands for HTML or XML). At this stage, Spring doesn't know that your Resource is XML
Document doc = doLoadDocument(inputSource, resource);
// Parse XML to register BeanDefinitions
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throwex; }...Copy the code
The definitions of doLoadBeanDefinitions are defined by the definitions of doLoadDocument (see definitions of doLoadBeanDefinitions)
The code block7
// I put the documentLoader in this location
private DocumentLoader documentLoader = new DefaultDocumentLoader();
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
/ / load the Document
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
Copy the code
As you can see from the above code, the documentLoader is an instance of DefaultDocumentLoader (I specifically put the member attribute in this location). In this loadDocument method, the most important parameter is the fourth parameter. This parameter is obtained by getValidationModeForResource, we first look at this method:
The code block8
protected int getValidationModeForResource(Resource resource) {
/ / to have set a good validation model, in the case of ClassPathXmlApplicationoContext, this value
// The value of validationModeToUse is 1 if you debug the code from the previous article
int validationModeToUse = getValidationMode();
// VALIDATION_AUTO is 1, so don't go if
// (VALIDATION_AUTO represents a way for Spring to discover what validation_resources should be used)
if(validationModeToUse ! = VALIDATION_AUTO) {return validationModeToUse;
}
DetectedMode returned 3 after the debug code started in the previous article
// If we were using a DTD format file, 2 would be returned
int detectedMode = detectValidationMode(resource);
if(detectedMode ! = VALIDATION_AUTO) {return detectedMode;
}
// Return XSD verification mode, which is also 3
return VALIDATION_XSD;
}
Copy the code
Then the previous loadDocument method takes this parameter and parses the inputSource of the first parameter into an XML Document object based on this parameter. For space, I won’t put the code for this method here. Then look at the registerBeanDefinitions(Document Doc, Resource Resource) method in XmlBeanDefinitionReader, which is essentially the second line from block 6:
The code block9
@SuppressWarnings("deprecation")
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
/ / 1. Use reflection to create DefaultBeanDefinitionDocumentReader instance objects
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2. Set the environment
documentReader.setEnvironment(getEnvironment());
// 3. Get the number of registered BeanDefinitions
int countBefore = getRegistry().getBeanDefinitionCount();
// 4. Actually register BeanDefinition, the actual process of parsing XML
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// Returns the number of registered BeanDefinitions
return getRegistry().getBeanDefinitionCount() - countBefore;
}
Copy the code
The focus is on the fourth line of code, we can see that real parsing XML or to BeanDefinitionDocumentReader registerBeanDefinitions method to do this class, this method has two parameters, The first is the XML Document object that we converted earlier. The second is actually an XmlReaderContext (which we’ll leave out in extensions). Let’s look at the registerBeanDefinitions method:
The code block10
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
/ / is to take all the root node and then handed over to the real doRegisterBeanDefinitions to parsing
// There are a lot of similar styles in spring source code, but doXXX is where the real work is...
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
Copy the code
Then look at real work doRegisterBeanDefinitions method
The code block11
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
// This if block deals with profiles (ignore)
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if(! getEnvironment().acceptsProfiles(specifiedProfiles)) {return; }}// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
// Since beans tags can be nested within beans tags, recursive calls to root are different, so we need to create a new delegate
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
preProcessXml(root);
// Start from root, (core logic)
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
Copy the code
See parseBeanDefinitions of core logic
The code block12
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
/ / is looking at the tag to have "http://www.springframework.org/schema/beans", if you have, that is not the developers to define their own XML, if you go
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)) {
// Parse the default tags
parseDefaultElement(ele, delegate);
}
else {
// Parse custom tags (more on custom tags later)delegate.parseCustomElement(ele); }}}}else {
// Parse custom tags (more on custom tags later)delegate.parseCustomElement(root); }}Copy the code
In this article, we will only look at parseDefaultElement. This method is the own tag of Spring IOC. I will make a separate article about the custom tag:
The code block13
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// Parse the import tag
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// Parse the alias tag
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// Parse the bean label
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// How to parse beans?
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recursedoRegisterBeanDefinitions(ele); }}Copy the code
In fact, you can see that in spring XML, only import, alias, bean, beans belong to IOC itself, and the rest belong to custom tags. Now let’s look at parsing the import label importBeanDefinitionResource method:
The code block14
// Parse the import tag
protected void importBeanDefinitionResource(Element ele) {
// Get the location property value and check if it is empty (new version property name is changed to resource)
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
if(! StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele);
return;
}
// If the location has something like ("${user.dir}"), you need to parse this first
location = getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
// Determine whether location is a relative path or an absolute path
boolean absoluteLocation = false;
try {
// The logic here is to see if the location starts with classpath*: or classpath: or if the location is a URL or if you are an absolute or relative path based on the scheme of the URI
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
}
// If it is an absolute path, load the parse Resource directly with loadBeanDefinitions in xmlBeanDefinitionReader
// If it is not an absolute path, calculate the relative path, and then load the parse Resource with loadBeanDefinitions in xmlBeanDefinitionReader
if (absoluteLocation) {
try {
// Note that getReaderContext().getreader () returns the xmlBeanDefinitionReader object
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); }}catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex); }}else {
// Resolve Resource relative to path
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// Use the loadBeanDefinitions method above to determine the relative path of the Resource
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
// If relative path parsing fails, try URL parsing
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); }}catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex); }}// After parsing, the listener is activated, but in this version, nothing is done, because the final method is an empty implementation, so it does not matter here
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
Copy the code
So this seems like a very long method, and it’s a little bit complicated, but basically you take the location attribute of the import tag and you check it, and then you check whether location is a relative path or an absolute path, and then you parse it differently depending on the path, But ultimately you’re going to have to use loadBeanDefinitions(Resource Resource), The check for import loop references in the loadBeanDefinitions(EncodedResource EncodedResource) method that is then called in this method refers to this one, as mentioned in block 5.
Parsing the import tag, let’s look at the code for parsing the alias tag:
The code block15
protected void processAliasRegistration(Element ele) {
// Get the alias and name attributes in the alias tag
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
// Validates the name attribute. If it is empty, an exception is thrown in the error method
boolean valid = true;
if(! StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele);
valid = false;
}
// Validates the alias attribute. If it is empty, an exception is thrown in the error method
if(! StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
// If valid, register the alias
if (valid) {
try {
// The specific alias parsing logic will be covered in a later article
getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex); } getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); }}Copy the code
As for beans tag is to use the aforementioned doRegisterBeanDefinitions method, namely code block 11, actually is the recursive analysis. The most important processBeanDefinition method, which parses a bean label into a concrete BeanDefinition object, is shown below
The code block16
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// Parsing the bean label is also the most important logic
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if(bdHolder ! =null) {
// 1. Parse the custom label under the default label
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 2. Register beanDefiniton 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(newBeanComponentDefinition(bdHolder)); }}Copy the code
There we see the first line by first assigned to parse the bean label method, this also is the most important, it is a direct call his class parseBeanDefinitionElement (Element ele, BeanDefinition containingBean). The second argument is passed null, so let’s look at this method:
The code block17
// Start parsing the
tag
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 1. Get the bean id and name attributes
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// Put name into the collection, which will later be registered as an alias
List<String> aliases = new ArrayList<String> ();if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 2. If there is no id attribute, the first of the name attributes is used as the bean's unique identifier name in the container
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"); }}// 3. Check whether the bean name is unique
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// This is the beanDefinition
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if(beanDefinition ! =null) {
// If beanName is still empty or a string is empty, beanName is generated as a rule (not important)
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); }}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;
}
Copy the code
The above code blocks are divided into four parts, the first three parts are very simple, let’s take a look at the fourth part of the parseBeanDefinitionElement (ele, beanName containingBean) this overloads, note at this point the third parameter is still passed is null:
The code block18
// containingBean is null
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
// 1. Get the full path of the bean class name
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
// get the id of the parent bean
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
// 3. So far, all paths of parent and class are found
// bd has only class and parentName and parentName
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 4. Set attributes in the bd tag
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
// 5. Lookup -method and replace-method,
// lookup-method = @lookup; // lookup-method = @lookup
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
Handle constructor-arg, property, qualifier tags
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;
}
Copy the code
Now that there are methods to parse each tag, let’s take a look at the method finally called in the third block above:
The code block19
public static AbstractBeanDefinition createBeanDefinition(
String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
// The parent beanDefnition name
bd.setParentName(parentName);
if(className ! =null) {
if(classLoader ! =null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
else{ bd.setBeanClassName(className); }}return bd;
}
Copy the code
As you can see, our bean tag is ultimately resolved to an instance of GenericBeanDefinition, and almost all of its member properties come from AbstractBeanDefinition, We already mentioned these properties at the beginning of this article, so you can check them out if you don’t understand. Then look at the code in block 18, fourth:
The code block20
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
BeanDefinition containingBean, AbstractBeanDefinition bd) {
/ / the scope attribute
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
this.readerContext.warning("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}else if(containingBean ! =null) {
bd.setScope(containingBean.getScope());
}
/ / the abstract properties
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
/ / lazy - init properties
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
// This property is obsolete from 3.0, and can be injected with the constructor
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
// The tokely-on attribute corresponds to the current annotation DependOn.
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// Autowire-candidate property, which indicates whether this bean can be used as an object to inject into other beans
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if(candidatePattern ! =null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); }}else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// paimary, referring to the preference of the bean to be used by autowiring
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
/ / init - method attribute
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
if (!"".equals(initMethodName)) { bd.setInitMethodName(initMethodName); }}else {
if (this.defaults.getInitMethod() ! =null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false); }}/ / destory properties
Init method and deStory are the same as post Construct and pre deStory annotations
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (!"".equals(destroyMethodName)) { bd.setDestroyMethodName(destroyMethodName); }}else {
if (this.defaults.getDestroyMethod() ! =null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false); }}/ / factory - method attribute
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
/ / factory - bean properties
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
Copy the code
The code for the methods 5 and 6 in block 18 is very simple and will not be posted to keep the article short. In using delegation parseBeanDefinitionElement after parsing XML attributes in the already have been read to beanDefinition object above. Next up is decorating custom properties and custom tags, and I’ll talk about that in custom tags. After decorating the custom, you register the BeanDefinition object, and the PARSING of the XML is complete.
ClassPathApplicationContext and BeanDefinitionReader
I don’t know if you remember the code at the beginning of the last post:
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath*:application-context.xml");
ATest aTest = applicationContext.getBean(ATest.class);
aTest.doSomeThing();
}
Copy the code
This code ClassPathApplicationContext will our parses XML loaded into beanDefinition objects, and transformed into the final bean, Now that we know how XmlBeanDefinitionReader parses XML into beanDefinition objects, where does this parsing take place while this code is running? We take a look at the structure of the ClassPathApplicationContext method:
The code block21
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
// Call the method below
this(new String[] {configLocation}, true.null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
// Parse the configuration with [classpath*:application-context.xml]
setConfigLocations(configLocations);
if (refresh) {
// Call the refresh method belowrefresh(); }}Copy the code
Note: I'm only showing the first few lines of Refresh for now, because the rest is not relevant to what we're talking about so farCopy the code
The code block22
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. Preparation before loading
prepareRefresh();
// 2. Get a new beanFactory instance
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. Initialize beanFactory and set various Settings for it
prepareBeanFactory(beanFactory);
Copy the code
Put out only the beginning of that a few lines of code, because we now say, also with the second sentence, we direct the obtainFreshBeanFactory of 2, it is also in AbstractApplicationContext,
The code block23
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
/ / the key
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ":" + beanFactory);
}
return beanFactory;
}
Copy the code
The focus is on the first line of this method, this method is also AbstractApplicationContext, but it is an abstract method, the real implementation is in AbstractRefreshableApplicationContext, Let’s look directly at the refreshBeanFactory method here:
The code block24
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// Load the BeanDefinition
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory; }}catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for "+ getDisplayName(), ex); }}Copy the code
Only a block of code I gave above notes, that is also an abstract method, the real implementation is in subclasses AbstractXmlApplicationContext, we to AbstractXmlApplicationContext:
The code block25
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader (XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
/ / 2. It is in AbstractXmlApplicationContext, its parent class inherited DefaultResourceLoader already, also
// Implements ResourcePatternResolver
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 3. Set the validation model
initBeanDefinitionReader(beanDefinitionReader);
// 4. Load Resource
loadBeanDefinitions(beanDefinitionReader);
}
Copy the code
Here we can see directly, use XmlBeanDefinitionReader ClassPathXmlApplicationContext is to load the parsing of the Resource, let’s take a look at the above code block 3 in the code
The code block26
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
Copy the code
This calls the internal method of XmlBeanDefinitionReader. Note that the this. valtionReader argument contains the value true.
The code block27
public void setValidating(boolean validating) {
this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
this.namespaceAware = ! validating; }Copy the code
This is where the validationMode value, which appears in blocks 7 and 8, is assigned to the application to find out what parsing method to use. Finally, let’s look at the code at 4 in block 25
The code block28
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if(configResources ! =null) {
reader.loadBeanDefinitions(configResources);
}
// Actually go here, because configResources above is null
String[] configLocations = getConfigLocations();
if(configLocations ! =null) { reader.loadBeanDefinitions(configLocations); }}Copy the code
This method calls the XmlBeanDefinitionReader method we talked about earlier. Now, we’ve done our best to define how XML parses to BeanDefinition. Let’s summarize what we’ve talked about so far in this graph:
We can see that this is, in fact, the bottom part, and in the next part we’ll talk about how to go from BeanDefinition to Bean instance.