Followed by an understanding and source code interpretation of spring IOC (I)
refresh method
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); contextRefresh.end(); } } }
OK, look at the code line by line
first line
synchronized (this.startupShutdownMonitor) {
This line is obvious. Lock the member variable startupShutdownMonitor. What is the member variable?
Lock an object to prevent multiple threads from performing initialization operations at the same time
Second line
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
Create a static inner class object DefaultStartupStep
Third line
prepareRefresh();
Before refreshing, set some parameters, such as setting the start timestamp, the flag of whether the context is activated, output the refresh context information, and verify some necessary properties.
protected void prepareRefresh() { // Switch to active. this.startupDate = System.currentTimeMillis(); this.closed.set(false); this.active.set(true); if (logger.isDebugEnabled()) { if (logger.isTraceEnabled()) { logger.trace("Refreshing " + this); } else { logger.debug("Refreshing " + getDisplayName()); } } // Initialize any placeholder property sources in the context environment. initPropertySources(); // Validate that all properties marked as required are resolvable: // see ConfigurablePropertyResolver#setRequiredProperties getEnvironment().validateRequiredProperties(); // Store pre-refresh ApplicationListeners... if (this.earlyApplicationListeners == null) { this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); } else { // Reset local application listeners to pre-refresh state. this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Allow for the collection of early ApplicationEvents, // to be published once the multicaster is available... this.earlyApplicationEvents = new LinkedHashSet<>(); }
Set the start time for timing. Set flags, output logs, initialize listeners, check environment variables, create event LinkedHashSet, etc.
The core method of checking environment variables is
getEnvironment().validateRequiredProperties(), in short, throw an exception if the value of the environment variable is empty, and then stop starting Spring
Fourth line
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
Click in
Let's look at the refreshBeanFactory method again
It is in this method that the original BeanFactory of the application context is destroyed and the DefaultListableBeanFactory is created. This buddy has a large background and inherits all the container interfaces and abstract classes.
Take a look at the customizeBeanFactory method
We can see from the name that it is used to set the two configuration properties of BeanFactory: whether to allow Bean overwrite and whether to allow circular reference.
Next is a super important method, loadbean definitions
Before looking at this method, we must understand one thing: BeanDefinition.
We know that BeanFactory is a Bean container, and BeanDefinition is a part of Bean
It contains the class pointed to by the Bean, whether it is singleton, whether it is lazy to load, the dependency of the Bean and other related attributes. The BeanDefinition is saved in BeanFactory.
Take a look at the BeanDefinition interface
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // Bean's life cycle only provides sington and prototype by default, //There will also be requests and sessions in the WebApplicationContext, //globalSession, application, websocket, etc String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; // Set parent Bean void setParentName(String parentName); // Get parent Bean String getParentName(); // Set the class name of the Bean void setBeanClassName(String beanClassName); // Gets the class name of the Bean String getBeanClassName(); // Set the scope of the bean void setScope(String scope); String getScope(); // Set lazy loading void setLazyInit(boolean lazyInit); boolean isLazyInit(); // Set all beans that this Bean depends on void setDependsOn(String... dependsOn); // Returns all dependencies of the Bean String[] getDependsOn(); // Set whether the Bean can be injected into other beans void setAutowireCandidate(boolean autowireCandidate); // Can this Bean be injected into other beans boolean isAutowireCandidate(); // For multiple implementations of the same interface, if no name is specified, Spring will preferentially select the bean whose primary is set to true void setPrimary(boolean primary); // Is it primary boolean isPrimary(); // Specify factory name void setFactoryBeanName(String factoryBeanName); // Get factory name String getFactoryBeanName()// Specify the factory method name in the factory class void setFactoryMethodName(String factoryMethodName)// Get the factory method name in the factory class String getFactoryMethodName(); // Constructor parameters ConstructorArgumentValues getConstructorArgumentValues(); // The attribute value in the bean, which will be mentioned later when injecting the attribute value into the bean MutablePropertyValues getPropertyValues(); // singleton boolean isSingleton(); // prototype boolean isPrototype(); // If the bean is set to abstract, it cannot be instantiated and is often used as a parent bean for inheritance boolean isAbstract(); int getRole(); String getDescription(); String getResourceDescription(); BeanDefinition getOriginatingBeanDefinition(); }
Next, continue to look at the loadBeanDefinitions method
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); 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. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
The XmlBeanDefinitionReader is instantiated first. Because the xml file is used here, the XmlBeanDefinitionReader is used to load. It has three brothers.
This object contains xml validation, parsing of various tags, etc.
This method then populates the properties. After initialization, the main work is as follows
Continue to look at the loadbean definitions (bean definitionreader) method
The first if is to see if there is a configuration file specified by the system. If not, go. The second if loads the classpath we first passed in: application IOC XML into loadbean definitions
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = this.getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } else { int count; if (resourceLoader instanceof ResourcePatternResolver) { try { Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location); count = this.loadBeanDefinitions(resources); if (actualResources != null) { Collections.addAll(actualResources, resources); } if (this.logger.isTraceEnabled()) { this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException var6) { throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6); } } else { Resource resource = resourceLoader.getResource(location); count = this.loadBeanDefinitions((Resource)resource); if (actualResources != null) { actualResources.add(resource); } if (this.logger.isTraceEnabled()) { this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } } }
The specific details will not be explained. Go to the method in the figure below
We will come to the loadBeanDefinitions method of XmlBeanDefinitionReader.
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Loading XML bean definitions from " + encodedResource); } Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get(); if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } else { int var6; try { InputStream inputStream = encodedResource.getResource().getInputStream(); Throwable var4 = null; try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } catch (Throwable var24) { var4 = var24; throw var24; } finally { if (inputStream != null) { if (var4 != null) { try { inputStream.close(); } catch (Throwable var23) { var4.addSuppressed(var23); } } else { inputStream.close(); } } } } catch (IOException var26) { throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var26); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } return var6; } }
This method contains the doLoadBeanDefinitions method
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = this.doLoadDocument(inputSource, resource); int count = this.registerBeanDefinitions(doc, resource); if (this.logger.isDebugEnabled()) { this.logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException var5) { throw var5; } catch (SAXParseException var6) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6); } catch (SAXException var7) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7); } catch (ParserConfigurationException var8) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8); } catch (IOException var9) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9); } catch (Throwable var10) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10); } }
This method is very important. This method converts the xml file into a Document file and registers the bean. Next, look at the key parsing method registerBeanDefinitions.
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //Build a tool class to read Document BeanDefinitionDocumentReader documentReader =createBeanDefinitionDocumentReader(); //Gets the number of registered bean s int countBefore = getRegistry().getBeanDefinitionCount(); // Look down here documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //The total registered beans minus the previously registered beans are the beans registered this time return getRegistry().getBeanDefinitionCount() - countBefore;}
registerBeanDefinitions->doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = this.createDelegate(this.getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute("profile"); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; "); if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource()); } return; } } } this.preProcessXml(root); this.parseBeanDefinitions(root, this.delegate); this.postProcessXml(root); this.delegate = parent; }
Next, look at parsebean definitions ()
Look at the parseDefaultElement() method
Resolve import, alias, bean, beans
Look at the processBeanDefinition method in parsing Bean
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException var5) { this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5); } this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
The main thing of this method is to create BeanDefinition.
Look at parseBeanDefinitionElement
@Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute("id"); String nameAttr = ele.getAttribute("name"); List<String> aliases = new ArrayList(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; "); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(id) && !aliases.isEmpty()) { beanName = (String)aliases.remove(0); if (this.logger.isTraceEnabled()) { this.logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { this.checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (this.logger.isTraceEnabled()) { this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]"); } } catch (Exception var9) { this.error(var9.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } else { return null; } }
Mainly look at these lines
parseBeanDefinitionElement method
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName,BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } // Create BeanDefinition and set class information AbstractBeanDefinition bd = createBeanDefinition(className, parent); // Set a bunch of properties of BeanDefinition, which are defined in AbstractBeanDefinition parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); /*** The following pile is parsing < bean ></ bean> After the internal sub element * is parsed, the information is put into the bd attribute */ // Parse < meta / > parseMetaElements(ele, bd); // Parse < lookup method / > parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // Parse < replaced method / > parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // Parse < constructor Arg / > parseConstructorArgElements(ele, bd); // Resolve < property / > parsePropertyElements(ele, bd); // Parse < qualifier / > parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
At this point, the BeanDefinition is created.
Here's a look at registering registerBeanDefinition
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 var8) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var8); } } BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!this.isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } if (existingDefinition.getRole() < beanDefinition.getRole()) { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (this.logger.isTraceEnabled()) { this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (this.hasBeanCreationStarted()) { synchronized(this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; this.removeManualSingletonName(beanName); } } else { this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; } if (existingDefinition == null && !this.containsSingleton(beanName)) { if (this.isConfigurationFrozen()) { this.clearByTypeCache(); } } else { this.resetBeanDefinition(beanName); } }
This method doesn't work
It is worthy of being Gaofu Shuai DefaultListableBeanFactory, and the member variables are as follows.
// JSR-330 support private static Class<?> javaxInjectProviderClass; // Cache referenced by DefaultListableBeanFactory private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories = new ConcurrentHashMap<>(8); // Serial number id private String serializationId; // Allow different definitions to be re registered with the same name private boolean allowBeanDefinitionOverriding = true; // Allow lazy loading private boolean allowEagerClassLoading = true; // Dependent sort order private Comparator<Object> dependencyComparator; // Parser to check whether the bean definition is a candidate for automatic assembly private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver(); // Mapping of dependency types and auto injection values private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16); // Mapping of BeanDefinition and beanName private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); // Mapping of dependency types to singleton and non singleton bean s private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); // Mapping of dependency types and singleton bean s private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64); // bean definition name list, arranged in registration order. private volatile List<String> beanDefinitionNames = new ArrayList<>(256); // Examples of manual registration are listed in the order of registration private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16); // Fixed configuration cached bean definition name array private volatile String[] frozenBeanDefinitionNames; // Can I cache bean definition metadata for all beans private volatile boolean configurationFrozen = false;
So far, the Bean container has been initialized, and the configuration has been converted to beandefinitions, and then all beandefinitions have been registered to beanDefinitionMap.
I'm dazzled. Fortunately, I'm young and can stand it... Continue to be more beautiful tomorrow!