Why start looking at spring source code
I have been writing code halfway through careers for almost a year and a half. I have been using the spring framework since I started working. Although I can use it and build it, I often don’t understand the principles behind it, such as: how spring controls transactions, how springmvc handles requests, and how AOP is implemented... This makes people feel very uneasy, so start to slowly study the source code of spring while reading the book!!!
How to efficiently view the source code
My answer is to look at the source code with specific questions, otherwise it is very easy to get stuck in the source code details and then you will faint. Finally, you find out what you have been reading for a long time.
introduction
Spring is an open source design-level framework that solves the loose coupling problem between the business logic layer and other layers, and integrates interface-oriented programming ideas throughout the entire system application. It is also one of the essential skills in Java work...
Since it records the Spring source code analysis process, I will not elaborate on the detailed usage.
Core code
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version></dependency>
usage
public class Application { public static void main(String[] args) { BeanDefinitionRegistry beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); ClassPathResource resource = new ClassPathResource("bean.xml"); //The entry point for the entire resource loading. reader.loadBeanDefinitions(resource); }}Decryption
DefaultListableBeanFactory is the default implementation of Spring registration and loading beans. It can be called the ancestor in the entire Spring Ioc template.
Tracking the DefaultListableBeanFactory, you can find the following code blocks. What is the purpose of this design?
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class);}For example, when there is attribute B in A, Spring will automatically instantiate attribute B if it is found that attribute B is not instantiated when obtaining attribute A. This is also an important feature provided in Spring. In some cases, B will not be initialized, such as implementing the BeanNameAware interface.
Spring introduces this: when auto-assembly, ignore the given dependency interface, such as parsing the Application context registration dependencies through other methods, similar to the injection of BeanFactory through BeanFactoryAware or the injection of ApplicationContext through ApplicationContextAware.
Resource Management
The Resource interface is used to manage File, URL, Classpath and other resources. Resource is responsible for reading the configuration file, that is, encapsulating the configuration file into a Resource, and then handing it over to the XmlBeanDefinitionReader for processing.
XML parsing
XmlBeanDefinitionReader is an implementation of Spring resource file reading, parsing, and registering, and we should focus on this class.
Track reader.loadBeanDefinitions(resource); , we can see the following core code (exclude comments and throw exceptions)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 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(); } }}The above code first performed a coding operation on Resource, with the purpose of worrying about the encoding problem in XML
If you observe carefully InputSource inputSource = new InputSource(inputStream); , its package name is actually org.xml.sax, so we can conclude that Spring uses SAX parsing and uses InputSource to decide how to read XML files.
Finally, the prepared data is passed to the real core processing part through parameters doLoadBeanDefinitions(inputSource, encodedResource.getResource())
Get Document
1. doLoadBeanDefinitions(inputSource, encodedResource.getResource()); , omit several catches and comments
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); }} 2. doLoadDocument(inputSource, resource);
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());}First, you can get the verification mode (DTD or XSD) of the XML file through getValidationModeForResource. You can set the verification method yourself. By default, VALIDATION_AUTO is enabled, that is, the verification mode is automatically obtained. Read the XML file through InputStream and check whether it contains DOCTYPE words. If it contains it, it is DTD, otherwise it will return XSD.
Common XML file verification patterns are:
public class XmlValidationModeDetector { /** * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration). */ public static final int VALIDATION_DTD = 2; /** * Indicates that XSD validation should be used (found no "DOCTYPE" declaration). */ public static final int VALIDATION_XSD = 3; public int detectValidationMode(InputStream inputStream) throws IOException { }} An EntityResolver parameter is involved in this.documentLoader.loadDocument method
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {}What is EntityResolver? Official explanation: If a SAX application needs to implement custom processing of external entities, it must implement this interface and register an instance with the SAX drive using the setEntityResolver method. That is to say, for parsing an XML, sax will first read the declaration on the XML document, and search for the corresponding DTD definition according to the declaration, so as to verify the document, the default search rules (i.e., network download, download the DTD definition through the DTD URI address declared by XML), and perform authentication. The download process is a long process, and when the network is unavailable, an error will be reported here because the corresponding dtd has not been found.
The function of EntityResolver is that the project itself can provide a method of how to find DTD declarations, that is, the program implements the process of finding DTDs, which avoids finding corresponding declarations through the network.
3.EntityResolver accepts two parameters:
public abstract InputSource resolveEntity (String publicId,String systemId) throws SAXException, IOException;
3.1 Define bean.xml file, with the content as follows (XSD mode)
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans.xsd"></beans>
Parsed to the following two parameters:
3.2 Define bean.xml file, with the content as follows (DTD mode)
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans></beans>
Parsed to the following two parameters:
3.3 Spring uses DelegatingEntityResolver to parse EntityResolver
public class DelegatingEntityResolver { @Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }}We can see that different parsers are used for different modes
Register a Bean
After reading parsing XML verification, continue to track the code and see how Spring registers bean information based on Document
public class XmlBeanDefinitionReader { public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Create DocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // Record the number of BeanDefinitions before statistics int countBefore = getRegistry().getBeanDefinitionCount(); // Register BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // Record the number of loaded BeanDefinitions this time return getRegistry().getBeanDefinitionCount() - countBefore; }}When registering a bean, first use a BeanDefinitionParserDelegate class to determine whether it is the default namespace. The implementation is to determine whether the namespace uri is equal to the default uri:
public class BeanDefinitionParserDelegate { 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)); }} Track documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); , where doc is converted through loadDocument in the previous code block. The main purpose of this method is to extract root nodes (beans)
public class DefaultBeanDefinitionDocumentReader { @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }} Track doRegisterBeanDefinitions(root) and we will see the following processing flow
protected void doRegisterBeanDefinitions(Element root) { // ... String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); // ... // empty implementation preProcessXml(root); parseBeanDefinitions(root, this.delegate); // empty implementation postProcessXml(root); this.delegate = parent;}First, parse the profile (the more common way to play is that the bean objects initialized by different profiles are different, so they implement multiple environments)
The following parsing uses the template method mode, where preProcessXml and postProcessXml are both empty methods, so that the subsequent subclasses can perform some processing before and after parsing. Just override these two methods.
Analyze and register BeanDefinition, this part of the code is relatively simple
public class DefaultBeanDefinitionDocumentReader { /** * Resolve other nodes under the root node import", "alias", "bean". * @param root node name*/ 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)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } } /** * Process Beans Tags, and then register them in the registry to */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated 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(new BeanComponentDefinition(bdHolder)); } }} Delegate the parseBeanDefinitionElement method of the BeanDefinitionParserDelegate class for element parsing, and return the instance of the BeanDefinitionHolder type bdHolder (including the various attributes of the configuration file class, name, id, alias, etc.)
When the returned bdHolder is not empty, if there is a custom attribute in the child node of the default label, the custom label is parsed and parsed again, and then BeanDefinitionReaderUtils.registerBeanDefinition(); registers the bdHolder and sends the registration event, informing the relevant listening bean that the registration has been successful.
Summarize
After a few unknown autumn, winter, spring and summer, everything will follow the direction you want...
Okay, 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.
Say something
Full text code: https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1 (local download)