1, Foreword
In the previous blog, we introduced the otainfreshbeanfactor method in the refresh method. This method is used to obtain the bean factory. During this period, it will also complete the loading of the configuration file and generate the BeanDefinition. It should be noted that the default BeanFactory generated here is DefaultListableBeanFactory. It should be noted that, Some of our customized xsd constraint files are also parsed here by implementing the beandefinitionparser interface and the parse method. The customized tags are also parsed by implementing the NamespaceHandlerSupport interface and the init method. The following is the parsing flow chart of the bean configuration file I drew:
We need to learn how to parse the xsd custom sub section of the blog. If we don't need to parse the xsd custom sub section of the blog in the future, we need to know how to parse it.
2, Source code analysis of Spring configuration file parsing process
2.1 Warehousing: obtainFreshBeanFactory
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { /** * Refresh the bean factory to determine whether the bean factory already exists. If so, it needs to be destroyed and closed * The refreshBeanFactory method of AbstractRefreshableApplicationContext is called by default * When refreshing the bean factory, the bean definition will be loaded. * Initialize BeanFactory, read the XML file, and record the obtained BeanFactory in the properties of the current entity */ refreshBeanFactory(); return getBeanFactory(); }
Let's move on to the refreshBeanFactory method:
@Override protected final void refreshBeanFactory() throws BeansException { // Judge whether the bean factory exists. If it exists, it needs to be destroyed and closed first. Otherwise, there will be problems if (hasBeanFactory()) { // Destroy the bean and remove all beans in the bean factory from the map according to the bean name destroyBeans(); // Close the bean factory, set the serialization id of the bean factory to null, and assign the value of beanFactory to null closeBeanFactory(); } try { // Recreate the bean factory. The default returned bean factory type is DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); // Set serialization ID, ID: hexadecimal value of class name + "@" + object beanFactory.setSerializationId(getId()); // Custom bean factory. Function: set whether the bean definition can be overwritten and whether circular reference is allowed when the bean is created customizeBeanFactory(beanFactory); // Parse and load the bean definition. By default, it is implemented through loadBeanDefinitions in AbstractXmlApplicationContext class loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
The method will first determine whether there is beanFactory, if it exists, destroy the corresponding factory first, then call createBeanFactory to create a bean factory, default to the DefaultListableBeanFactory we created, and then set some attribute information, which is to parse the configuration file in loadBeanFinitions.
2.2 loadBeanDefinitions(beanFactory)
Create a bean reader in xml format and set some property values.
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create an Xml format bean reader, which will set the resource loader and environment information of the bean factory XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Object is initialized when creating a bean reader Get it directly here beanDefinitionReader.setEnvironment(this.getEnvironment()); // The above has been initialized during creation. You can get it directly here 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. // Initialize beanDefinitionReader object. Set whether the configuration file needs to be verified here initBeanDefinitionReader(beanDefinitionReader); // The tag attribute in the xml is parsed and added to the bean definition loadBeanDefinitions(beanDefinitionReader); }
Then look down at loadbean definitions (bean definition reader);
2.3loadBeanDefinitions(beanDefinitionReader)
There will be two branches here. One is to directly resolve resources and the other is to resolve the incoming configuration file list. Here we will take the second branch.
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // The container constructed by another constructor will use the method of configResources to load the bean definition Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // Get all the configuration file names, for example: {"beans.xml"}. Mainly the name of the xml configuration file String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } } @Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int count = 0; for (String location : locations) { // Load bean definition count += loadBeanDefinitions(location); } return count; } @Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
2.4 loadBeanDefinitions(location,null)
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // resourceLoader is a property set in the XmlApplicationContext through the setResourceLoader method 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 { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // Load bean definition 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; } } @Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int count = 0; for (Resource resource : resources) { // Load bean definition count += loadBeanDefinitions(resource); } return count; }
In fact, in the end, it is also called back to loadbean definitions (resource);
2.5loadBeanDefinitions(resource)
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // Perform bean definition loading 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(); } } }
The real work here is doloadbean finishes;
2.6 doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // Parse the xml configuration file into a Document object through JAXP(Java API for XMLProcessing) in JDK Document doc = doLoadDocument(inputSource, resource); // Register bean definitions int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
2.7 registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Create a Bean definition document reader through reflection. The default type is DefaultBeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // Get the number of bean definitions that have been loaded before, directly through beandefinitionmap Size acquisition int countBefore = getRegistry().getBeanDefinitionCount(); // Execute xml parsing and bean definition registration. When creating the bean definition reader context, the default DefaultNamespaceHandlerResolver will be created documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // Returns the number of bean definitions registered this time return getRegistry().getBeanDefinitionCount() - countBefore; } // To resolve the registered bean definition @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; // Perform the bean defined registration operation doRegisterBeanDefinitions(doc.getDocumentElement()); }
2.8 doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) { // 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. BeanDefinitionParserDelegate parent = this.delegate; // Create a bean to define the parser delegate object, and resolve some global attributes in the < beans / > tag at the same time this.delegate = createDelegate(getReaderContext(), root, parent); 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; } } } // Handle some custom element types before spring parses xml preProcessXml(root); // Parse bean definition parseBeanDefinitions(root, this.delegate); // After spring parses the xml, it processes some custom element types postProcessXml(root); this.delegate = parent; }
You can see that preProcessXml and postProcessXml are empty implementations, which can be left for us to expand. This is an expansion point.
2.9 parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // Judge whether it is a user-defined label according to the uri of nameSpace. // The uri corresponding to the nameSpace of the beans of spring is: http://www.springframework.org/schema/beans 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)) { // Parsing the default xml nodes (import,alias,beans) in Spring parseDefaultElement(ele, delegate); } else { // Parsing custom label content delegate.parseCustomElement(ele); } } } } else { // If it is not the default namespace of spring, it will be resolved as an extension tag delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // Handle import tag parsing importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // Handle alias tag parsing processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // Handle bean tag parsing processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // Handle beans tag parsing doRegisterBeanDefinitions(ele); } }
Here you can see that if it is the default tag: import, bean, beans and alias, it will go through the default parsing, parseDefaultElement. Otherwise, it will go through parseCustomElement.
3, Custom profile resolution
3.1 User.java
public class User { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
3.2 # customize UserBeanDefinitionParse
@SuppressWarnings("rawtypes") protected Class getBeanClass(Element element) { return User.class; } protected void doParse(Element element, BeanDefinitionBuilder bean) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); if (StringUtils.hasText(userName)) { bean.addPropertyValue("userName", userName); } if (StringUtils.hasText(email)){ bean.addPropertyValue("email", email); } } }
3.3 UserNamespaceHandler
/** * MyNamespaceHandler */ public class UserNamespaceHandler extends NamespaceHandlerSupport{ /** * Resolution class of registration label */ @Override public void init() { registerBeanDefinitionParser("mqc:user",new UserBeanDefinitionParse()); } }
3.4. Create spring Handlers file
Create a META-INF directory under the resource directory, and create three files: spring handlers,Spring. schemas,user.xsd.
Spring.handlers:
http\://www.mqc.com/schema/user=org.springframework.mqc.selftag.UserNamespaceHandler
Spring.schemas:
http\://www.mqc.com/schema/user.xsd=META-INF/user.xsd
user.xsd:
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.mqc.com/schema/user" xmlns:tns="http://www.mqc.com/schema/user" elementFormDefault="qualified"> <element name="mqc"> <complexType> <attribute name ="userName" type = "string"/> </complexType> </element> </schema>
3.5 creating profiles
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aaa="http://www.mqc.com/schema/user" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.mqc.com/schema/user http://www.mqc.com/schema/user.xsd"> <aaa:mqc userName = "lee" /> </beans>
Here, the spring custom label is probably over. In fact, if there is a demand for custom labels in later companies, you can go to see the label analysis of spring context and do what spring does.