This article was first published in cdream Personal blog (click for more reading experience)
Welcome to reprint, reprint please indicate the source
As a java programmer, conservatively estimates that there are 300 days in a year to have close contact with Spring ~like me, I'm afraid every day to have Spring, so this time also need to do a thorough understanding! This time, let's see how Spring initializes the IoC container.
We all know that Spring provides rich functionality for enterprise applications, and the underlying functionality depends on the two core features of IOC and AOP at the bottom.
IOC implementation mainly includes two parts, one is the initialization of IOC container, the other is dependency injection. Because the two parts are relatively independent, they are divided into different articles. This article mainly describes the initialization of IOC container.
I. The Concept of IoC
Inversion of Control (IoC) is a design principle in object-oriented programming, which can be used to reduce the coupling between computer codes. One of the most common methods is called Dependency Injection (DI), and another is called Dependency Lookup. By controlling inversion, when an object is created, a reference to the object on which it depends is passed by an external entity that controls all objects in the system. In other words, dependencies are injected into objects.
The above concept comes from Wikipedia, and in Expert Spring MVC, Web Flow and Expert One-on-One J2EE without EJB, dependency injection is also considered as a way of IoC. But in some places, IoC and DI will be regarded as a concept (e.g. Spring in Action, Spring Disclosure, etc.), but it doesn't matter, it doesn't affect our understanding.
The normal operation of Class A requires Class B.
Before IoC, when we use a method of Class A, we always depend on some functions of Class B. So we have to go to new B objects. Sometimes we have to consider when to destroy them, when to use singleton mode and so on. Classes are less and better managed. If more classes are really smart, it will take the first nine of ten, and the dependency relationship between A and B will make generations. Code is tightly coupled. If class B has problems, or if class B doesn't use C at all one day, does the new B() in class A have to be replaced by new C()? Imagination feels tired.
With IoC, object creation is handled by third-party containers. B in A is accomplished by injection. B has problems or needs to be replaced by C. As long as B and C can be replaced by C (in reality, B and C can achieve the same interface solution ~so Spring is an Interface-oriented programming duck).
- Expert One-on-One J2EE without EJB was written by Spring's father Rod Johnson. Go into Spring's BeanFactory class and see if the author is him. Ha-ha!
- Control Inversion and Dependency Injection This is one of the best explanations I've ever seen for inversion of control. It's highly recommended!
II. Initialization of IoC Containers
Preparatory content
This section only describes the initialization of IoC containers, including creating containers and loading bean s into containers. The following three things are at the core of this section:
Source Location of BeanDefinitionLoading and parsing of BeanDefinition
Registration of BeanDefinition in Containers
Because the implementation of Spring's IoC container is too complex, it's easy for us to get caught up in details by calling between different classes. As a result, we go too far to forget why we should start. This article mainly discusses three main things when the container is initialized.
Let's start with a few concepts:
BeanFactory: This is the interface definition of IOC container. It provides the most basic function of IoC. If container is an automobile factory, then this structure specifies the most basic function of automobile factory. It can store parts and assemble automobiles.
public interface BeanFactory { /** * When using containers to retrieve bean s, add escape selfcharacters - you can retrieve the FactoryBean itself, but it's an object generated by Factory */ String FACTORY_BEAN_PREFIX = "&"; /** * Get the bean by its name */ Object getBean(String name) throws BeansException; /** * Get beans by the type and type of beans */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * Getting beans by bean type */ <T> T getBean(Class<T> requiredType) throws BeansException; /** * Get bean s by name and parameters */ Object getBean(String name, Object... args) throws BeansException; /** * Does it contain a bean named name? */ boolean containsBean(String name); /** * Is it a single case? */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * Prototype */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** * Is a bean named name targetType? */ boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; /** * Get the bean type with name */ Class<?> getType(String name) throws NoSuchBeanDefinitionException; /** * Gets a collection of aliases for bean s with name */ String[] getAliases(String name); }
Application Context: In addition to the above functions, the upgraded automobile factory also provides a lot of humanized services. It inherits the interfaces of MessageSource, Resource Loader, Application Event Publisher and so on, and adds a lot of support for advanced containers on the basis of simple IoC containers of BeanFactory.
public interface ApplicationContext extends EnvironmentCapable,ListableBeanFactory,HierarchicalBeanFactory,MessageSource,ApplicationEventPublisher,ResourcePatternResolver { /** * Returns the id(unique) of the context */ String getId(); /** * Returns the name of the application to which the context belongs */ String getApplicationName(); /** * Return this context-friendly name */ String getDisplayName(); /** * Returns the time when the context was first loaded */ long getStartupDate(); /** * Returns the parent context */ ApplicationContext getParent(); /** * Functional exposed automatic assembly plants, not commonly used */ AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }
There are not many ways to do this. The main way is in the inherited interface.
BeanDifinition: Store information about beans in Spring, including attribute names, class names, singletons, etc. It abstracts our definition of beans and is the main data type that makes containers work. For IOC containers, BeanDefinition is a data abstraction of object dependencies managed in inversion mode of control.
Next, I formally enter into the analysis of IoC container initialization. Take File System Xml Application Context as an example. Here is the inheritance relationship of File System Xml Application Context. (This shape, full of love, haha)
Source Location of BeanDefinition
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { /** * Non parametric structure */ public FileSystemXmlApplicationContext() { } /** * Input parent context */ public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } /** * Core construction methods, and several others are based on this construction method * configLocations Input xml configuration file location set * refresh Whether to refresh containers automatically (call to refresh method, core method of initializing context) * parent Parent context * 1.Input profile address * 2.Refresh container */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } /** * Locate BeanDefinition in the file system and return a FileSystem Resource with the given path * This method is called in the loadBeanDefinition of BeanDefinition Reader. * loadBeanDefinition Template pattern is adopted to implement in different subclasses (default is class path) */ @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
As you can see, all configLocation s are processed in this class, so that all BeanDefinition s in xml form are processed. This refresh is the most critical method, and then refresh is parsed.
Refresh is implemented in AbstractApplicationContext. Understanding the refresh method, you can basically understand the whole process of IoC initialization.
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Pre-refresh Preparatory Activities prepareRefresh(); // Key Approaches to Building beanFactory - > This approach will be explained in more detail next ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare for using beanFactory in this context prepareBeanFactory(beanFactory); try { // Setting up Post Processor postProcessBeanFactory(beanFactory); // Post processors that call beans are registered as beans in the context // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. // Register handlers created by intercepting bean s registerBeanPostProcessors(beanFactory); // Initialize message source for this context. // Initialization of message sources for context, internationalization initMessageSource(); // Initialize event multicaster for this context. // Time mechanism for initializing context initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. // Initialize other special bean s in special context subclasses onRefresh(); // Check for listener beans and register them. // Check the monitored bean s and register them in the container registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // Initialize all non-lazy loading singletons finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. // Publish related events to end refresh finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. // Abnormal destruction beans destroyBeans(); // Reset 'active' flag. // This activity is set to true in the prepare s above. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
Next, let's look at the container construction process in detail, in the class AbstractRefreshableApplicationContext
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // It's important to refresh the beanfactory, which is the beans it builds. refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
@Override protected final void refreshBeanFactory() throws BeansException { // If beanfactory already exists, destroy beans and shut down factories to avoid any impact on subsequent initialization if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // Here we create a DefaultListable BeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); // Set a unique id for serialization beanFactory.setSerializationId(getId()); // Custom bean factory customizeBeanFactory(beanFactory); // It's important to load BeanDefinition into the factory. loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
Load Bean Definitions is an abstract method in AbstractRefreshable Application Context. I directly found the implementation in the subclass AbstractXml Application Context (actually there are three implementation classes, but we are now studying the FileSystem Xml Application Context).
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Looking at the name, it's obvious that this guy pair is used to read and put information in the xml configuration file into the container. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //Configuration of the reader using environmental resources in this context beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow subclass personalization to initialize this reader initBeanDefinitionReader(beanDefinitionReader); // Then you start to load BeanDefinition really, [keep going in] loadBeanDefinitions(beanDefinitionReader); }
This time I'm going to jump into AbstractXml Application Context and read on.
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // Getting configuration resources ~~just returns a null directly, but there are subclasses to override (if you look at the HashMap source code, you must remember that there are several empty implementations for LinkedHashMap, the same is true here) Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // This is a method like AbstractRefreshable Config Application Context (jumping around the brain is too big...). ) // FileSystem Xml Application Context was set up before refresh String[] configLocations = getConfigLocations(); if (configLocations != null) { // At last, it began to analyse. reader.loadBeanDefinitions(configLocations); } }
The method in AbstractBean Definition Reade is loaded here.
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { // [Keep going inside] counter += loadBeanDefinitions(location); } return counter; }
It's still in this class. ~No jumping.
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { // Get the resource loader ResourceLoader resourceLoader = getResourceLoader(); // Empty throw anomaly if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } // This is used to parse classpath*: This path can be multiple configuration files if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //GettResource here [completes the specific positioning] Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // Start loading BeanDefinition int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // At this point, getResource encapsulates a lot of I/O-related operations in the resource interface. // So far [the specific positioning has been completed] Resource resource = resourceLoader.getResource(location); // Start loading BeanDefinition [Continue jumping in] int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
The refresh method completes the entire initialization of IoC. The refreshBeanFactory() method is very important. This section talks about positioning. The next section begins with BeanDefinition parsing and loading.
Loading and parsing of BeanDefinition
For the IoC container, this loading process is equivalent to the process of converting the Bean Definition in xml into a Spring internal data structure. The management and dependency injection functions of the IoC container for beans are implemented by various operations on the Bean Definition it holds. These BeanDefinition s are implemented through a HashMap.
Undertaking the above, loadBeanDefinitions() is the core method for loading and parsing BeanDefinitions. Specifically implemented in the XMLBean Definition Reader.
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { // Here's cyclic loading, [continue jumping in] counter += loadBeanDefinitions(resource); } return counter; }
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // Package Resources, provide different ways to read Resource files by encoding, [continue to jump inside] return loadBeanDefinitions(new EncodedResource(resource)); }
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } // Get the Loaded Resource Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } // To solve the problem of duplicate dependencies, the equals method of encodedResource has been rewritten if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } // Here I get IO ready to read BeanDefinition in XML try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // [Continue to jump in] 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(); } } }
This is implemented in XMLBean Definition Reader (not in other classes)
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // Here we take the document object of XML, parse it by the document Loader, and take a look at the steps with interest. // Although xml has been parsed, it does not follow the rules of bean s, so it needs to be parsed further. Document doc = doLoadDocument(inputSource, resource); // Here we start the detailed process of beanDefinition parsing, an important method, [Continue to jump in] return registerBeanDefinitions(doc, resource); } 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); } }
Still in this class
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Create BeanDefinition Document Reader to parse the document BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); // Specific analysis process, [continue to jump in] documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
Enter the DefaultBeanDefinitionDocumentReader class, get the root element in the document element, and continue to call doRegisterBeanDefinition for registration.
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); // [Continue to jump in] doRegisterBeanDefinitions(root); }
protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!this.environment.acceptsProfiles(specifiedProfiles)) { return; } } BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); // Analyzing BeanDefinition, the core logic of this method, [Continue to jump in] parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
// It's easy to see that this is an xml parsing process 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 logic is the core logic, [continue to jump in] parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // import Tags if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean Tags else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // Resolve the bean tag, [Continue to jump inside] processBeanDefinition(ele, delegate); } // beans Tags else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // This is BeanDefinition Holder, which contains BeanDefinition objects, beanname, alias collection and other information. // parseBeanDefinitionElement() This method parses the definition of bean s in xml and is interesting to learn more about. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register parsed BeanDefinition with the IoC container, [Continue jumping inside] 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)); } }
At this point, the parsing and loading of BeanDefinition in XML are complete, and then enter the registration section of bean s.
Registration of BeanDefinition in IoC Container
After positioning and loading, BeanDefinition has established corresponding data structures in IoC. In order to use these BeanDefinitions more friendly, it is necessary to register these BeanDefinitions in the IoC container.
This method is in the BeanDefinitionReaderUtils class
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); // This is obviously a way to register BeanDefinition, [keep jumping in] registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String aliase : aliases) { registry.registerAlias(beanName, aliase); } } }
Jump to the DefaultListableBeanFactory class, which was used to create the factory earlier
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } // To ensure data consistency, a synchronized thread lock is added to the registration. synchronized (this.beanDefinitionMap) { // Check if there are beans with the same name in the IoC container, and if the beans with the same name are not allowed to be overwritten, throw exceptions BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound."); } else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) { if (this.logger.isWarnEnabled()) { this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +" with a framework-generated bean definition ': replacing [" +oldBeanDefinition + "] with [" + beanDefinition + "]"); } } else { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean '" + beanName +"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } else { this.beanDefinitionNames.add(beanName); this.frozenBeanDefinitionNames = null; } // Install BeanDefinition as in BeanDefinition Map // [At this point, the Spring IoC container initialization completes ~] // The bean DeinitionMap is the Concurrent HashMap with an initial length of 64 this.beanDefinitionMap.put(beanName, beanDefinition); } resetBeanDefinition(beanName); }
Here, registration is completed. We created a bean factory and registered BeanDefinition in Map held by the IoC container. This information is the basis of control inversion.
Three, summary
This article begins with a brief explanation of the concept of IoC, then proceeds with File System Xml Application Context, and describes step by step the core logic from bean factory creation to Bean Definition registration in IoC according to the source code. Spring source code really has too many details, in the process of reading the source code, we must grasp the core logic.
This article is a summary of IoC in the process of learning Spring source code, hoping to help students who want to read the source code but don't know where to start! If there are any mistakes, I hope you will correct them.
!
Reference: