This time, understand the XML parsing principle of spring

Don't talk at night 2022-02-13 07:27:08 阅读数:673

time understand xml parsing principle

Preface

Spring It's already us Java Web Development is an essential framework , It greatly simplifies our development , Improved developer efficiency . meanwhile , Its source code is also for developers treasure , From this, we can learn excellent design ideas and elegant naming norms , But because of its huge system 、 Complex design is very difficult for people who are just beginning to read the source code . So before that, you have to make up your mind , No matter how difficult it is, you have to insist down ; secondly , It's better to put Design patterns Master skillfully ; Then be sure to draw more when you start reading the source code UML Class diagram and sequence diagram , Ask yourself why you design like this ? What are the benefits of this design ? Is there a better design ? Of course , Carsickness is inevitable , But again , must do Persevere (PS: Source version 5.1.3.RELEASE).

Text

be familiar with IOC Architecture

To learn Spring Source code , First we have to find the right entrance , How can I find this entrance ? Let's think about it first , stay Spring When the project starts ,Spring What has been done . Here I take the most primitive xml Configuration mode to analyze , So when the project starts , First of all, we must first location —— find xml The configuration file , After positioning, it must be load —— Load our configuration into memory , Finally, according to our configuration Instantiation ( This article only talks about the first two processes ). that Spring How to locate and load xml What about the documents ? What classes are involved ? Let's start with a class diagram :
 Insert picture description here
The graph is IOC The system diagram of , On the whole, you need to have a general impression , You can see everything IOC Are inherited , The advantage of this design is that any subclass IOC You can use the parent class directly IOC Loaded Bean, It's kind of like JVM Class loaded Parent delegate mechanism ; The red boxes are the important categories involved in this article , Need to focus on remembering their relationship .
The two most important classes in the diagram are BeanFactory and ApplicationContext, This is all. IOC The parent interface . among BeanFactory Provides the most basic pair of bean The operation of :
 Insert picture description here
and ApplicationContex Inherited BeanFactory, At the same time, he inherited MessageSourceResourceLoaderApplicationEventPublisher And other interfaces to provide internationalization Resource loading Event publishing And other advanced functions . We should think about normal times Spring load xml The document should be ApplicationContext Subclasses of , From the picture, we can see one called ClassPathXmlApplicationContext Class , I think we usually take xml Put it in classPath Next , So let's just start with this class , This is the benefit of good naming .

Explore the process of configuration loading

stay ClassPathXmlApplicationContext There are many construction methods in , One of them is to pass in a string ( That is, the relative path of the configuration file ), But in the end, the following construct is called :

 public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {

super(parent);
// Create a parser , analysis configLocations
setConfigLocations(configLocations);
if (refresh) {

refresh();
}
}

First, call the parent class constructor to set the environment :

 public AbstractApplicationContext(@Nullable ApplicationContext parent) {

this();
setParent(parent);
}
public void setParent(@Nullable ApplicationContext parent) {

this.parent = parent;
if (parent != null) {

Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {

getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}

Then parse the incoming relative path and save it to configLocations variable , Finally, call the parent class AbstractApplicationContext Of refresh Method to refresh the container ( This method is called whenever the container is started ), Let's focus on this method :

 public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// Prepare for container initialization 
prepareRefresh();
// analysis xml
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) {

if (logger.isWarnEnabled()) {

logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {

// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

This method is a typical Template method pattern The implementation of the , The first step is to prepare to initialize the container environment , This step is not important , The point is the second step , establish BeanFactory object 、 Load the parsing xml And encapsulate it into BeanDefinition Objects are completed in this step .

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {

refreshBeanFactory();
return getBeanFactory();
}

Click in to see if it calls refreshBeanFactory Method , But there are two implementations , Which class should I enter ?
 Insert picture description here
If you remember the previous inheritance system , Then you will enter without hesitation AbstractRefreshableApplicationContext Class , So in the process of reading the source code, we must remember the class inheritance system .

 protected final void refreshBeanFactory() throws BeansException {

// If BeanFactory Not empty , Then remove BeanFactory And the examples 
if (hasBeanFactory()) {

destroyBeans();
closeBeanFactory();
}
try {

// establish DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// Set whether circular dependency is possible allowCircularReferences
// Is it allowed to re register different... With the same name bean Realization .
customizeBeanFactory(beanFactory);
// analysis xml, And put xml The label in is packaged as BeanDefinition object 
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {

this.beanFactory = beanFactory;
}
}
catch (IOException ex) {

throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

In this method, the last created... Will be cleared first BeanFactory And object instances , And then I created one DefaultListableBeanFactory Object and passed in loadBeanDefinitions In the method , This is also a template method , Because our configuration has more than xml, And notes, etc , So here we should enter AbstractXmlApplicationContext Class :

 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

// establish xml The parser , Here is a delegation model 
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
// Here's a this go in , because ApplicationContext Is to implement the ResourceLoader Interface 
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);
// Mainly look at this method 
loadBeanDefinitions(beanDefinitionReader);
}

First, I created a XmlBeanDefinitionReader object , See the name and know the meaning , This is parsing xml Class , It should be noted that the constructor of this class receives BeanDefinitionRegistry object , And here it will be DefaultListableBeanFactory Object passed in ( Don't forget that this object implements BeanDefinitionRegistry Class ), If you're sensitive enough , It should be conceivable that this class will be delegated to register later . Register for what ? It's registration, of course BeanDefintion. Remember this conjecture , We'll see if that's the case later .
Then enter the loadBeanDefinitions Method to get the previously saved xml Profile path , And entrust to XmlBeanDefinitionReader Object parsing load :

 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {

Resource[] configResources = getConfigResources();
if (configResources != null) {

reader.loadBeanDefinitions(configResources);
}
// Get the... That needs to be loaded xml The configuration file 
String[] configLocations = getConfigLocations();
if (configLocations != null) {

reader.loadBeanDefinitions(configLocations);
}
}

Finally, you will enter the abstract parent class AbstractBeanDefinitionReader in :

 public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {

// What we get here is still DefaultListableBeanFactory object 
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {

throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {

// Resource pattern matching available.
try {

// Put string type xml File path , Form like :classpath*:user/**/*-context.xml, convert to Resource object type , In fact, it's using stream 
// To load the configuration file , And then it's packaged as Resource object 
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// Mainly look at this method 
int count = loadBeanDefinitions(resources);
if (actualResources != null) {

Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {

logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
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 count = loadBeanDefinitions(resource);
if (actualResources != null) {

actualResources.add(resource);
}
if (logger.isTraceEnabled()) {

logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}

This method will mainly xml The configuration is loaded into memory and encapsulated into Resource object , This step is not important , You can skip , The main thing is loadBeanDefinitions Method , Finally, it is called to the subclass XmlBeanDefinitionReader Methods :

 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

try {

// obtain Resource Object xml FileStream object 
InputStream inputStream = encodedResource.getResource().getInputStream();
try {

//InputSource yes jdk Medium sax xml File parsing object 
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {

inputSource.setEncoding(encodedResource.getEncoding());
}
// Mainly look at this method 
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {

inputStream.close();
}
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {

try {

// hold inputSource Encapsulated into Document File object , This is a jdk Of API
Document doc = doLoadDocument(inputSource, resource);
// Mainly look at this method , According to the analysis document object , Take the label element inside and seal it into BeanDefinition
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {

logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

// establish DefaultBeanDefinitionDocumentReader object , And entrust it to do parsing and registration 
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// Mainly look at this method , We need to pay attention to createReaderContext Method 
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public XmlReaderContext createReaderContext(Resource resource) {

// XmlReaderContext Object XmlBeanDefinitionReader Objects and DefaultNamespaceHandlerResolver References to objects , I'll use 
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

Take a look at DefaultBeanDefinitionDocumentReader How to parse in :

 protected void doRegisterBeanDefinitions(Element root) {

// Created BeanDefinitionParserDelegate object 
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// If it is Spring Native namespace , First analysis profile label , It doesn't matter here 
if (this.delegate.isDefaultNamespace(root)) {

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {

String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {

if (logger.isDebugEnabled()) {

logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
// Mainly look at this method , Label specific parsing process 
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}

In this approach, focus on preProcessXmlparseBeanDefinitionspostProcessXml Three methods , among preProcessXml and postProcessXml It's all empty , It means that we can expand the operations to be performed before and after parsing the tag , It is also a template method pattern , Embodies the Spring High scalability of . Then enter parseBeanDefinitions Method to see how the tag is parsed :

 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)) {

// Default label resolution 
parseDefaultElement(ele, delegate);
}
else {

// Custom tag resolution 
delegate.parseCustomElement(ele);
}
}
}
}
else {

delegate.parseCustomElement(root);
}
}

Here are two kinds of tag parsing :Spring Native tags and Custom tag . How to distinguish these two labels ?

// Custom tag 
<context:component-scan/>
// Default label 
<bean:/>

Above , The prefix is the custom label , Otherwise, it would be Spring Default label , No matter which label is used, it needs to be in Spring Of xml The configuration file declares Namespace URI, In this way, when parsing the label, you can pass Namespace URI Find the corresponding NamespaceHandler.

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/beans

isDefaultNamespace Determine whether it is the default label , Click in to see if it is consistent with what I said above :

 public boolean isDefaultNamespace(Node node) {

return isDefaultNamespace(getNamespaceURI(node));
}
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public boolean isDefaultNamespace(@Nullable String namespaceUri) {

return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

You can see http://www.springframework.org/schema/beans The corresponding is the default label . next , We enter parseDefaultElement Method :

 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {

//import Label parsing 
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {

importBeanDefinitionResource(ele);
}
//alias Label parsing 
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {

processAliasRegistration(ele);
}
//bean label 
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {

processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {

// recurse
doRegisterBeanDefinitions(ele);
}
}

It's mainly right import、alias、bean Label parsing and beans Recursive parsing of word labels , Mainly look at bean Parsing of tags :

 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

// analysis elment Encapsulated in the BeanDefinitionHolder object 
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {

// The function of this method is not important , Mainly understand the design idea : Decorator design pattern and SPI design idea 
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {

// complete document To BeanDefinition After object conversion , Yes BeanDefinition Object for cache registration 
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

// obtain id and name attribute 
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// Get alias properties , Multiple aliases are available ,; separate 
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 (!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");
}
}
// Check beanName Whether to repeat 
if (containingBean == null) {

checkNameUniqueness(beanName, aliases, ele);
}
// The specific parsing and encapsulation process is still in this method 
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.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;
}
// bean Parsing 
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {

this.parseState.push(new BeanEntry(beanName));
// obtain class Name and parent class name 
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 {

// establish GenericBeanDefinition object 
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// analysis bean Attributes of the tag , And set the parsed properties to BeanDefinition In the object 
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// analysis bean Medium meta label 
parseMetaElements(ele, bd);
// analysis bean Medium lookup-method label 
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// analysis bean Medium replaced-method label 
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// analysis bean Medium constructor-arg label 
parseConstructorArgElements(ele, bd);
// analysis bean Medium property label 
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
return null;
}

bean The parsing steps of tags are not complicated to understand carefully , Is to load the values of tag attributes into BeanDefinition In the object , Here we need to pay attention to parseConstructorArgElements and parsePropertyElements Method , That's right. constructor-arg and property Parsing of tags , After the parsing is completed, the BeanDefinition Object's constructorArgumentValues and propertyValues in , And these two properties are next c and p Tag parsing will also use , It also involves a very important design idea —— Decorator mode .
Bean After the tag parsing is completed, it will generate BeanDefinition object 、bean The name and alias are encapsulated in BeanDefinitionHolder Object and return , And then it calls decorateBeanDefinitionIfRequired Decorate :

 public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {

BeanDefinitionHolder finalDefinition = definitionHolder;
// according to bean Label attribute decoration BeanDefinitionHolder, such as <bean class="xx" p:username="dark"/>
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {

Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
// according to bean Label sub element decoration BeanDefinitionHolder\
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {

Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {

finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}

In this method, the Bean Tag properties and child tag iterations , Get the custom tag and parse it , And decorate the previously created BeanDefinition object , Like the following c and p:

// c: and p: Represents through constructors and properties setter Method to assign a value to an attribute , yes constructor-arg and property The simplified way of writing
<bean class="com.dark.bean.Student" id="student" p:username="Dark" p:password="111" c:age="12" c:sex="1"/>

The two steps are the same , Let's go in decorateIfRequired In the method :

 public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

// according to node Get node The namespace of , Form like :http://www.springframework.org/schema/p
String namespaceUri = getNamespaceURI(node);
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {

// Get... According to the configuration file namespaceUri The corresponding processing class ,SPI thought 
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {

// call NamespaceHandler Processing class decorate Method , Start the specific decoration process , And return the decorated object 
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {

return decorated;
}
}
else if (namespaceUri.startsWith("http://www.springframework.org/")) {

error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
}
else {

// A custom namespace, not to be handled by Spring - maybe "xml:...".
if (logger.isDebugEnabled()) {

logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}

It's the same as we said before , First, get the... Corresponding to the tag namespaceUri, And then through this Uri To get the corresponding NamespceHandler, Last call NamespceHandler Of decorate How to decorate . Let's take a look at getting NamespceHandler The process of , This involves a very important idea of high scalability ——SPI( of SPI, Articles before me Dubbo——SPI And the principle of adaptive extension It has been explained in detail in , No more details here ):

 public NamespaceHandler resolve(String namespaceUri) {

// obtain spring All in jar The inside of the bag "META-INF/spring.handlers" file , And establish a mapping relationship 
Map<String, Object> handlerMappings = getHandlerMappings();
// according to namespaceUri:http://www.springframework.org/schema/p, Get the processing class of this namespace 
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {

return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {

return (NamespaceHandler) handlerOrClassName;
}
else {

String className = (String) handlerOrClassName;
try {

Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {

throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// To call a processing class init Method , stay init Method to complete the registration of the label element resolution class 
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
}
}
// AOP The label corresponds to NamespaceHandler, You can find NamespaceHandler The function of is to manage and register the tag parser related to yourself 
public void init() {

// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

Seeing here, we should know Spring How to parse xml And what we should do if we want to expand our own tags . Just create one of our custom tags and parsing classes , And specify its namespace and NamespaceHandler, Last in META-INF/spring.handlers Specify the namespace and in the file NamespaceHandler The mapping relationship of , It's like Spring Of c and p The label is the same :

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

Use... Like this SPI The idea of designing our project , When you need to expand , You don't need to change any code , Very convenient and elegant .
next , We go back to handler Of decorate Method , There are three default implementation classes :NamespaceHandlerSupportSimpleConstructorNamespaceHandlerSimplePropertyNamespaceHandler. The first is an abstract class , It has nothing to do with our process here , What you are interested in can be understood by yourself , The second and third are c and p The label corresponds to NamespaceHandler, The processing logic of the two decorations is basically the same , What I'm entering here is SimpleConstructorNamespaceHandler class :

 public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {

if (node instanceof Attr) {

Attr attr = (Attr) node;
String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));
String argValue = StringUtils.trimWhitespace(attr.getValue());
ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();
boolean ref = false;
// handle -ref arguments
if (argName.endsWith(REF_SUFFIX)) {

ref = true;
argName = argName.substring(0, argName.length() - REF_SUFFIX.length());
}
ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);
valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));
// handle "escaped"/"_" arguments
if (argName.startsWith(DELIMITER_PREFIX)) {

String arg = argName.substring(1).trim();
// fast default check
if (!StringUtils.hasText(arg)) {

cvs.addGenericArgumentValue(valueHolder);
}
// assume an index otherwise
else {

int index = -1;
try {

index = Integer.parseInt(arg);
}
catch (NumberFormatException ex) {

parserContext.getReaderContext().error(
"Constructor argument '" + argName + "' specifies an invalid integer", attr);
}
if (index < 0) {

parserContext.getReaderContext().error(
"Constructor argument '" + argName + "' specifies a negative index", attr);
}
if (cvs.hasIndexedArgumentValue(index)) {

parserContext.getReaderContext().error(
"Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." +
" Only one approach may be used per argument.", attr);
}
cvs.addIndexedArgumentValue(index, valueHolder);
}
}
// no escaping -> ctr name
else {

String name = Conventions.attributeNameToPropertyName(argName);
if (containsArgWithName(name, cvs)) {

parserContext.getReaderContext().error(
"Constructor argument '" + argName + "' already defined using <constructor-arg>." +
" Only one approach may be used per argument.", attr);
}
valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
cvs.addGenericArgumentValue(valueHolder);
}
}
return definition;
}

It's simple , Get c The value corresponding to the tag , Encapsulated into ValueHolder, Add to BeanDefinition Of ConstructorArgumentValues Attribute to , So the decoration is finished .
At this point, you might think , This is the same as usual Decorator mode Not quite the same. . Actually , What design patterns really want to express is the ideas represented by various patterns , Instead of blindly copying the implementation , Only by using its ideas flexibly can we really master the design pattern , The essence of decorator mode is Dynamic attributes 、 function 、 Responsibility is attached to the object , In this way, you can see whether the idea of decorator is used here ?
Return to... After decoration BeanDefinitionHolder Object and call BeanDefinitionReaderUtils.registerBeanDefinition Method to cache the object , Wait for the container to instantiate . Here is to cache it to DefaultListableBeanFactory Of beanDefinitionMap Properties of the , Just look at the code yourself , I won't post code . thus ,Spring Of XML The analysis principle is finished , Here is the sequence diagram I drew , You can compare it with :
 Insert picture description here

summary

This is Spring The first part of source code analysis , Just analyzed refresh Medium obtainFreshBeanFactory Method , We can see that it's just right XML The analysis and bean Defined registration cache ,Spring So much has been done , And taking into account the various areas that may be expanded , What about the projects we usually do ? Have you thought deeply behind the seemingly simple ?

copyright:author[Don't talk at night],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/02/202202130727050787.html