Preface
With the increasing popularity of Spring Boot, most of the configurations in the application are hidden. We only need to care about the real business content, Controller, Service, Repository, and when you pick up the keyboard, you can use a copy of the business code. The specific Component Scan, View, PlaceHolder... can be left behind. But in fact, this zero configuration is not long in Java application development. "It's difficult to get from extravagant to frugality." Many developers have experienced the lengthy Spring XML configuration, and it's really hard to go back to this configuration.
But sometimes, since the configuration content is not well implemented in a zero configuration like Spring Boot, we need to still use the XML configuration form and then importSource in. Or some projects are affected by the environment and are not developed using Boot, so they also need to have a certain understanding of the configuration.
Then let's look back at some of the custom Schema content in XML configuration, how some custom Schema content is integrated into Spring for configuration. For example:
Spring data es
dubbo
There are many such examples that we will not list them one by one. But through the above two figures, we find a common feature:
So when do these custom configurations work? How to verify whether the configuration is correct?
Let's look at Spring containing a file named spring.handlers. All custom extensions take effect through this file. Spring official aop and tx also have this principle.
Where is this file?
As shown in the figure above, it is META-INF/spring.handlers. The file content is also super simple:
http/://www.springframework.org/schema/data/elasticsearch=org.springframework.data.elasticsearch.config.ElasticsearchNamespaceHandler
The first is the prefix of each schema, and the next is the parsing class corresponding to the schema. When is the spring.handlers file loaded?
This also happens when parsing the custom configuration file. There is a resolve process. At this time, all spring.handlers files corresponding to the current classLoader will be loaded.
Let's look at this analysis class again, the content is as follows:
public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport { public ElasticsearchNamespaceHandler() { } public void init() { RepositoryConfigurationExtension extension = new ElasticsearchRepositoryConfigExtension(); RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension); this.registerBeanDefinitionParser("repositories", parser); this.registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser()); this.registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser()); } } First, it is inherited from NamesapceHandlerSupport
Then a series of parsers are registered in the rewritten init method. Each parser corresponds to a string, which is the custom content we use in the xml configuration file, such as the es configuration above
<elasticsearch:transport-client id="client" cluster-nodes="192.168.73.186:9300" cluster
The configuration here will eventually be parsed through TransportClientBeanDefinitionParser
The above-mentioned parsers are stored in a map in the init method
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
The so-called registration of parsers is to put the map of this parsers.
Looking back, what is the most core content in Spring? It is Bean. In fact, the contents we configure in XML will eventually be generated in a corresponding bean. All configurations are just to generate beans. These custom configurations are called BeanDefinition.
Therefore, when Spring parsing the configuration file, there will be the following judgment: whether it is defaultNamespace. If not, go to customElementParse
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)) { this.parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } Instead, the judgment that is not defaultNameSpace is more direct: does the namespace uri have content, or is it a statement of spring beans
public boolean isDefaultNamespace(String namespaceUri) { return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri); } So the requests all went to parseCustomElement, and the configuration analysis started. When parse, the corresponding Handler was found through uriResolver
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containsBd) { String namespaceUri = this.getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if(handler == null) { this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } } At this time, the only thing that is returned is the Handler configured in spring.handlers, and each Handler registers a lot of parse, and a process of obtaining parsers is needed.
public BeanDefinition parse(Element element, ParserContext parserContext) { return this.findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); if(parser == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; } The process of obtaining this is to get the corresponding parser in the map we start declaring through the string passed in, and then use it for configuration parsing.
With parser, the process of generating BeanDefinition is followed.
We see that these parsers are inherited from AbstractBeanDefinitionParser, or implement the interface of BeanDefinitionParser. The interface parse method is the entrance to unified parsing.
public class TransportClientBeanDefinitionParser extends AbstractBeanDefinitionParser { public TransportClientBeanDefinitionParser() { } protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TransportClientFactoryBean.class); this.setConfigurations(element, builder); return this.getSourcedBeanDefinition(builder, element, parserContext); } } In the rewritten parseInternal method, return the corresponding BeanDefinition after parsing the configuration. This is also a place where various frameworks are freely abstracted. For example, some frameworks are simple and straightforward, while some will apply some design patterns such as strategies and decorators to provide more flexibility.
After obtaining the BeanDefinition, put it in the entire Context to generate the content of Spring Bean, and we will analyze it later.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.