Understanding and source code interpretation of spring IOC

Posted by xeno on Thu, 23 Dec 2021 20:26:24 +0100

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!

Topics: Java Spring