1 Introduction
When using Spring there is always confusion about how Spring manages Spring bean s.
There is also a lot of mixed information on the Internet. Here I record some of the content that I have separated from the book after reading "Spring Source Code In-depth Analysis" recently.
Speaking of Spring, I have to say that in ancient times, when Spring was in its prime, XML was still used, and the annotation method was only available for subsequent optimization.
Here is also from the Spring Xml way.
2. Examples
Create a simple maven project first.
- add maven dependencies
The Spring version used here is 5.1.7. In fact, only a few core packages of Spring are needed in the initial stage. For the convenience of the follow-up, they are added here at one time.
Note: The scope of spring-test and Junit here is the default test, so the test code is in test.
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring-version>5.1.7.RELEASE</spring-version> </properties> <dependencies> <!--log--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> </dependency> <!--ioc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <!--test--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> <scope>test</scope> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.7.2</version> </dependency> <!--aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.5</version> </dependency> </dependencies>
- Java Bean
public class User { private int sex; private String username; private int age; // omit set/get }
- Configuration XML
Here is a brief explanation:
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance represents an XMLschema instance
- http://www.springframework.org/schema/beans represents the storage location of the bean namespace and can be accessed directly.
- http://www.springframework.org/schema/beans/spring-beans-3.2.xsd stands for XSD xml parsing file.
<?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 http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <bean id="user" class="com.fans.entity.User"/> </beans>
- test code
The test code is written under the project src/test/java:
public class SpringTest { @Test void test(){ BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("user.xml")); User user = (User) beanFactory.getBean("user"); System.out.println(user); } }
Test result: com.fans.entity.User@2f7c2f4f
- code structure
3. Analysis
Note: Only the case of resource loading is parsed here, and will continue to be added later
Starting from the sentence BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("user.xml"));:
3.1,Resource
new ClassPathResouce("user.xml") is the loading of the resource.
In Java, resources from different sources are abstracted into URLs, and the reading logic of resources from different sources is handled by registering different handlers (URLStreamHandler). Generally, the handler type is identified by using different prefixes (Protocol), such as "file:","jar:","http:", etc. However, the URL does not define the handler of resources such as relative classpath or ServletContext by default, Although you can register your own URLStreamHandler to parse URL prefixes (protocols), such as classpath:, it requires understanding the implementation mechanism of the protocol, and the URL does not provide basic URL methods, such as checking whether the current Resource exists and whether the current Resource is readable. Therefore, Spring implements its own abstract structure for the resources used internally: the Resource interface encapsulates the underlying resources
The above quotation is from the book. We can know that new ClassPathResource(“user.xml”) in the demo test code is the resource loading of the configuration information in user.xml, which is convenient for us to parse the content later.
View the source code:
- new ClassPathResouce("user.xml")
public ClassPathResource(String path) { this(path, (ClassLoader) null); } public ClassPathResource(String path, @Nullable ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); }
This is mainly to deal with the path problem in "user.xml", StringUtils.cleanPath, pathToUse.substring, etc.
3.2, XmlBeanFactory
- new XmlBeanFactory(new ClassPathResource("user.xml"))
public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
XmlBeanFactory(Resource resource) points to XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) for parsing, where the builder is used to create objects.
- super(parentBeanFactory)
Here XmlBeanFactory points to the parent class DefaultListableBeanFactory, and DefaultListableBeanFactory points to the parent class AbstractAutowireCapableBeanFactory.
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); } public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) { this(); setParentBeanFactory(parentBeanFactory); }
Here ignoreDependencyInterface(Class<?> clazz) The above is to ignore the incoming class during autowiring.
super() points to the parent class AbstractBeanFactory by AbstractAutowireCapableBeanFactory.
setParentBeanFactory(parentBeanFactory) is also implemented in AbstractBeanFactory, mainly injecting parent BeanFactory.
3.3,loadBeanDefinitions
this.reader.loadBeanDefinitions(resource) Here is the processing of resources, in which the parsing and loading of resources are completed by this sentence. This is not the loading done by XmlBeanFactory itself, but using its own property XmlBeanDefinitionReader to complete, click in to see:
@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.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try (InputStream inputStream = encodedResource.getResource().getInputStream()) { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
EncodedResource encodes the resource to satisfy the encoding of the input stream below.
encodedResource.getResource().getInputStream() to get the resource's data stream.
new InputSource(inputStream) then encapsulates the resource stream object obtained.
doLoadBeanDefinitions(inputSource, encodedResource.getResource()) This is where the actual content is loaded.
- doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { // The catch content is omitted, it is the processing of parsing exceptions, and only the main process is concerned here. } }
The parsing and loading here is divided into two steps:
(1) Parse and convert into Document object
Document doc = doLoadDocument(inputSource, resource);
(2) Execute registration and count the number of registered bean s
int count = registerBeanDefinitions(doc, resource);
- doLoadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
Before converting the object, we actually do the process of obtaining the parser and verifying:
getEntityResolver() Gets the resolver, here the project provides a way to find and parse the XML DTD file.
getValidationModeForResource is used to determine whether the XML is parsed by custom, DTD or XSD.
- loadDocument
loadDocument actually completes the conversion of XML files to Document through the default implementation class of DocumentLoader, DafaultDocument.
@Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isTraceEnabled()) { logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
The main function of createDocumentBuilderFactory here is to create a factory class:
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { factory.setValidating(true); if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { // Enforce namespace aware for XSD... factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { ParserConfigurationException pcex = new ParserConfigurationException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); pcex.initCause(ex); throw pcex; } } } return factory; }
Then create a Builder through the factory class, set the parser, and handle exceptions:
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } return docBuilder; }
The above two parts are to create parsing objects. The real parsing is through parse method, builder Parse (inputsource), in the DocumentBuilder implementation class DocumentBuilderImpl, here is to parse XML files through DomParser. DomParser is not the focus of our discussion. Here, I am interested in can be deeply understood
public Document parse(InputSource is) throws SAXException, IOException { if (is == null) { throw new IllegalArgumentException( DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "jaxp-null-input-source", null)); } if (fSchemaValidator != null) { if (fSchemaValidationManager != null) { fSchemaValidationManager.reset(); fUnparsedEntityHandler.reset(); } resetSchemaValidator(); } domParser.parse(is); Document doc = domParser.getDocument(); domParser.dropDocumentReferences(); return doc; }
- registerBeanDefinitions
After the parsing of the Document is completed, it is the processing of the Document. Now what is actually done is to convert the XML configuration file into a Document. The following is the content of continuing to parse:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
- First obtain the statistics of the number of beans registered in the registry
- Complete the registration through BeanDefinitionDocumentReader, this is the main content
- Count the number of registered bean s
- registerBeanDefinitions
Check out the default implementation class DefaultBeanDefinitionDocumentReader of BeanDefinitionDocumentReader :
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); } 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; 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; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
Create the bean's holding object through createDelegate, and then through parseBeanDefinitions to finally complete the parsing and registration of the bean. The parsing and registration of all tags are included, and the next part will explain the details.
4. Timing diagram
When I checked it before, it took a lot of time to make a sequence diagram (if you can't see clearly, you can click to enlarge):
5. Summary
When writing the above, there are also some details that I did not pay attention to, but the main process is complete. If you want to be very detailed, it is recommended to read the original book.
In addition, the internal implementation of Spring is very complex, and we are also struggling to look at it, but there is always something to be gained by slowly combing it.
Come on, let's work together!