Spring IOC Source Analysis - Preparations before refreshing

Posted by cocell on Sat, 01 Jun 2019 01:52:05 +0200

Catalog

Registration of ClassPathXml Application Context

Source code analysis based on Spring 4.3

From the ClassPath Xml Application Context entry, it will eventually be invoked to

/*
     * Create a new ClassPath Xml Application Context with a given parent to load definition information from a given XML file.
     * Load all bean definition information and create all singletons
     * Alternatively, the refresh is called manually after further configuring the context.
*/
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
  throws BeansException {

  super(parent);
  setConfigLocations(configLocations);
  if (refresh) {
    refresh();
  }
}

The above comment explains that all bean s in the initialization process are singletons during the start-up of the container

auto refresh

ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");

Is equivalent to

Manual refresh

ApplicationContext context = new ClassPathXmlApplicationContext();
context.register("xxx.xml");
context.refresh();

There are three links mentioned above. Here's an analysis.

Loading parent and child containers

  • First, the method of loading and initializing the parent container

  1. The first is ClassPath Xml Application Context, which is an independent application context that retrieves the context definition file from the classpath and parses the normal path into the class path resource name containing the package path. It supports Ant-Style (Path Matching Principle), which is the context of a one-stop application. Consider using the Generic Application Context class in conjunction with XmlBean Definition Reader to set up a more flexible context configuration.

Ant-Style path matching principles, such as "mypackages/application-context.xml" can be replaced by "mypackages/*-context.xml".

Note: If there are multiple contextual configurations, the subsequent bean definition overrides the previously loaded files. This can be used to deliberately override some bean definitions through additional XML files

  1. It wasn't a complete somebody, AbstractXml Application Context, that came along slowly afterwards to facilitate the implementation of Application Context (an important idea of abstract classes is adaptation). The main function of AbstractXml Application Context is to parse the configuration file registered with ClassPath Xml Application Context by creating an XML reader. It has two main methods: loadBean Definitions (Default Listable Bean Factory Bean Factory) and loadBean Definitions (XmlBean Definition Reader reader)

  2. The next slowly appearing player is AbstractRefreshable Config Application Context, which acts like a middleman and does not do much work, much like the ancient prime minister's memorial to the emperor, whose role is equivalent to that of a memorial. It serves as a base class for XML application context implementations, such as ClassPath Xml Application Context, FileSystem Xml Application Context, and Xml Web Application Context.
  3. AbstractRefreshable ApplicationContext plays the role of secretarial when bosses generally listen to secrets. It is the base class of ApplicationContext and supports multiple calls to refresh() methods, creating a new internal bean factory instance each time. The only way to inherit AbstractRefreshable Application Context is to load Bean Definitions, every time a refresh method is called. A concrete implementation is the DefaultListable BeanFactory that loads the bean definition information.
  4. But it's not good for a secretary to give his employer his resignation. In the middle, there's a technical leader to look at the overall situation, to discuss the company's development plan with the boss, and to fight a hard battle under the leadership of new people to do projects (this man is really charming haha ha), but the technical leader can't do all the work. He also needs to hand over to his programmers to help him complete the specific work, and the programmers take over. When you get to a job, see if there are reusable projects and open source libraries, and find that they are available, just link the "references" to them. This is the initial work of the container, but the process is not over yet, and you have to keep in mind that you are working for the boss.

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
  // Work assigned to other programmers
  this();
  // Know who your boss is
  setParent(parent);
}

public AbstractApplicationContext() {
  this.resourcePatternResolver = getResourcePatternResolver();
}

// Return to ResourcePatternResolver to parse matching patterns in resource instances. By default, PathMatching ResourcePatternResolver supports Ant-Style patterns.
protected ResourcePatternResolver getResourcePatternResolver() {
  return new PathMatchingResourcePatternResolver(this);
}

// At this point, the resourceLoader is the ClassPathXmlApplicationContext object.
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
  Assert.notNull(resourceLoader, "ResourceLoader must not be null");
  this.resourceLoader = resourceLoader;
}

You need some programmers to help you do specific coding work. You also need to make sure that you are an employee of the company and that you need to listen to the boss, so you need to know who the boss is.

@Override
public void setParent(@Nullable ApplicationContext parent) {
  this.parent = parent;
  if (parent != null) {
    Environment parentEnvironment = parent.getEnvironment();
    if (parentEnvironment instanceof ConfigurableEnvironment) {
      getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
    }
  }
}

But at this time, the boss is away on business (because the parent is null), so you need to make some decision s by yourself. So far, the analysis of the first line has been completed.

Configuration Path Analysis

  • The second line, setConfigLocations(configLocations) in Application Context
// Parameters pass in variable parameters, variable parameters are an array, that is, you can pass multiple configuration files, separated by ",".
public void setConfigLocations(@Nullable String... locations) {
  if (locations != null) {
    Assert.noNullElements(locations, "Config locations must not be null");
    // Conglocations is an empty String array that can be null and registered manually for null.
    this.configLocations = new String[locations.length];
    // Parse the path of each configuration file in the array.
    for (int i = 0; i < locations.length; i++) {
      this.configLocations[i] = resolvePath(locations[i]).trim();
    }
  }
  // The default is to create a parameter-free constructor for ClassPathXml Application Context directly, using manual registration.
  else {
    this.configLocations = null;
  }
}

Key Points: Path Resolution Method: ResolvevePath (locations [i]). trim () in AbstractRefreshable Config Application Context; see how path resolution works

// Resolve the given path and replace placeholders with corresponding environmental attribute values if necessary. Applied to path configuration.
protected String resolvePath(String path) {
  return getEnvironment().resolveRequiredPlaceholders(path);
}

Involving two methods, getEnvironment() and validateRequiredProperties() in AbstractRefreshableConfig Application Context, let's start with the first

  • getEnvironment()

    // Return the Environment in the application context as a configuration to further customize
    // If not specified, the default environment is initialized.
    @Override
    public ConfigurableEnvironment getEnvironment() {
      if (this.environment == null) {
        // Use default environment configuration
        this.environment = createEnvironment();
      }
      return this.environment;
    }
    • Let's see how createEnvironment() initializes the default environment:

      // Create and return a Standard Environment, subclass overrides this method to provide
      // A custom Configurable Environment implementation.
      protected ConfigurableEnvironment createEnvironment() {
              // Standard Environment inherits AbstractEnvironment, while AbstractEnvironment inherits AbstractEnvironment.
              // Implementation of Configurable Environment
              return new StandardEnvironment();
          }
      

      In fact, it's very simple. It's just a new standard environment () constructor. What is Standard Environment? Standard implementation of environment for non-web applications. He implements the AbstractEnvironment Abstract class, following is the concrete inheritance tree:

Standard Environment is the concrete implementation of Abstract Environment, and Abstract Environment inherits the Configurable Environment interface and provides the concrete implementation of some methods. Connfigurable Environment inherits Environment, while Environment and Configurable Property Resolver inherit Property Resolver at the same time.

Let's look at the source code for StandardEnvironment():

public class StandardEnvironment extends AbstractEnvironment {

  // System Attribute Resource Name
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

  // JVM System Attribute Resource Name:
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

  // Customize appropriate property files for standard Java environments
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

}


Now the reader will have a question, not to say that new came out with a standard implementation of Standard Environment, but there is no default construction method for Standard Environment? What is this?

In fact, the standard environment is constructed by AbstractEnvironment:

public AbstractEnvironment() {
  // The way to implement custom attribute resources is customize Property Sources () in Standard Environment.
  customizePropertySources(this.propertySources);
  if (logger.isDebugEnabled()) {
    logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
  }
}


The above `customize Property Sources'is implemented by `Standard Environment', as follows

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
  propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
  propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}


Since the container was null when it started, after adding the system environment and system properties, it becomes as shown in the following figure
    • How to acquire system attributes and how to acquire system environment has not been followed down. Interested readers can continue to use them.

      Roughly a screenshot of the image, which has roughly the same attributes

      systemProperties

      systemEnvironment

  • Another is resolveRequiredPlaceholders, which is defined by the Property Resolver super-top interface
// Resolve the ${} placeholder in a given text parameter and replace it with the corresponding attribute value of getProperty parsing.
// An unresolved placeholder without a default value causes an Illegal ArgumentException to be thrown.
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    • Implemented by the AbstractPropertyResolver subclass, and see the inheritance tree of AbstractPropertyResolver

    -

    The specific implementation methods are as follows:

    // The text passed in is the parsed configuration file SimpleName
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
      if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
      }
      return doResolvePlaceholders(text, this.strictHelper);
    }
    
    // Call createPlaceholder Helper
    private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
            return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
                    this.valueSeparator, ignoreUnresolvablePlaceholders);
    }
    
    ----------------------------PropertyPlaceholderHelper-------------------------------
    
      // Property Placeholder Helper loads the following special characters
      static {
            wellKnownSimplePrefixes.put("}", "{");
            wellKnownSimplePrefixes.put("]", "[");
            wellKnownSimplePrefixes.put(")", "(");
        }
    
    /*
        Create a new Property Placeholder Helper using the provided prefix and suffix
         * Parametric interpretation: prefix at the beginning of placeholder Prefix placeholder
         *         placeholderSuffix A suffix at the end of a placeholder
         *         valueSeparator Separator between placeholder variables and associated default values
         *         ignoreUnresolvablePlaceholders Indicates whether unresolved placeholders should be ignored.
    */
    
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                     @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
    
      Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
      Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
      this.placeholderPrefix = placeholderPrefix;
      this.placeholderSuffix = placeholderSuffix;
      String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
      if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
        this.simplePrefix = simplePrefixForSuffix;
      }
      else {
        this.simplePrefix = this.placeholderPrefix;
      }
      this.valueSeparator = valueSeparator;
      this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }
    
    

    After parsing the placeholder, you need to do a real parsing and call the doResolvePlaceholders method in AbstractPropertyResolver.

    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
      return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
          return getPropertyAsRawString(placeholderName);
        }
      });
    }
    
    

    Placeholder Resolver is an internal class of the PropertyPlaceholder Helper class. This is an anonymous internal class. What it really calls is the replacePlaceholders method in the PropertyPlaceholder Helper, which is as follows:

    // Replace the placeholder in the format of ${name} with the value returned from the Placeholder Resolver provided.
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
      Assert.notNull(value, "'value' must not be null");
      return parseStringValue(value, placeholderResolver, new HashSet<String>());
    }
    
    
    protected String parseStringValue(
                String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
    
            StringBuilder result = new StringBuilder(value);
    
            int startIndex = value.indexOf(this.placeholderPrefix);
    
        // Determine whether the specified placeholder ${exists and returns directly if it does not.
            while (startIndex != -1) {
                int endIndex = findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    if (!visitedPlaceholders.add(originalPlaceholder)) {
                        throw new IllegalArgumentException(
                                "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                    }
                    // Recursive invocation, parsing placeholders contained in the placeholder key.
                    placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                    // Now obtain the value for the fully resolved key...
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                    if (propVal == null && this.valueSeparator != null) {
                        int separatorIndex = placeholder.indexOf(this.valueSeparator);
                        if (separatorIndex != -1) {
                            String actualPlaceholder = placeholder.substring(0, separatorIndex);
                            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }
                    if (propVal != null) {
                        // Recursive invocation, parsing placeholders contained in the
                        // previously resolved placeholder value.
                        propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                        result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Resolved placeholder '" + placeholder + "'");
                        }
                        startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                    }
                    else if (this.ignoreUnresolvablePlaceholders) {
                        // Proceed with unprocessed value.
                        startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                    }
                    else {
                        throw new IllegalArgumentException("Could not resolve placeholder '" +
                                placeholder + "'" + " in value \"" + value + "\"");
                    }
                    visitedPlaceholders.remove(originalPlaceholder);
                }
                else {
                    startIndex = -1;
                }
            }
    
            return result.toString();
        }
    

    To put it bluntly, the above process is used to determine whether there is a ${placeholder or not. If there is a placeholder, go to the following decision logic and put ${}.

    The value in Placeholder Resolver is replaced by the value returned by Placeholder Resolver, and if not, it is returned directly.

Container refresh

After these preparations are completed, the next steps are the core steps of IOC, DI and AOP, and the soul of Spring framework. Because of too many source codes and too wide design scope, this article only analyses what refresh pretreatment should do: We all know that no matter what context you load, refresh() method of AbstractApplication Context will eventually be invoked. This method is the core method of loading, parsing, registering and destroying. It adopts the idea of factory design.

// Complete the creation and initialization of IoC container
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
      
      // 1: Preparations before refreshing.
            prepareRefresh();

            // Tell subclasses to refresh the internal bean factory.
      //  2: Create the IoC container (DefaultListableBeanFactory) and load the parsed XML file (eventually stored in the Document object)
      // Read the Document object and complete the loading and registration of BeanDefinition
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      //  3: Preprocess the IoC container (set some common properties)
            prepareBeanFactory(beanFactory);

            try {
    
                //  4: Allow post-processing of bean factories in context subclasses.
                                postProcessBeanFactory(beanFactory);

                //  5: Call BeanFactoryPostProcessor Post Processor to process BeanDefinition
                invokeBeanFactoryPostProcessors(beanFactory);

                //  6: Register BeanPostProcessor Post Processor Processor
                registerBeanPostProcessors(beanFactory);

                //  7: Initialize some message sources (such as processing internationalized i18n and other sources)
                initMessageSource();

                //  8: Initialize Application Event Multicast
                initApplicationEventMulticaster();
        
                //  9: Initialize some special bean s
                onRefresh();

                //  10: Register some listeners
                registerListeners();

                //  11: Instantiate the remaining single bean s (non-lazy loading)
        //      Note: Bean's IoC, DI, and AOP all happen in this step
                finishBeanFactoryInitialization(beanFactory);

                //  12: When the refresh is completed, the corresponding events need to be published
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy created instances to avoid resource usage
                destroyBeans();

                // Reset the'active'tag.
                cancelRefresh(ex);

                // Propagating exceptions to callers
                throw ex;
            }

            finally {

                // Reset the common introspective cache in the Spring core, because we may no longer need the metadata of the singleton bean.
                resetCommonCaches();
            }
        }
    }

Refresh Pretreatment of Refresh Container

The main function of this step is to prepare the refresh context, set the start time and the activity flag as the role of initialization of attribute resources.

protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize placeholder attribute resources in environment context
        initPropertySources();
        // Verify that all attributes marked as required are resolvable
        getEnvironment().validateRequiredProperties();

        // Allow collection of early Application Events
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

There are two codes to illustrate: initPropertySources is a method that requires subclasses to implement, and by default nothing will be done; getEnvironment() is a method that returns directly because the createEnvironment has been created by default during the source analysis process described above.

@Override
public ConfigurableEnvironment getEnvironment() {
  if (this.environment == null) {
    this.environment = createEnvironment();
  }
  return this.environment;
}

Below is the validate Required Properties () analysis, not anxious, see the source code can not be anxious, to look at the world with a very good mood.

Firstly, the validateRequiredProperties method is defined in the Configurable Property Resolver interface.

// Verify that each property set by setRequiredProperties exists and parse the non-null value, it throws
// MissingRequiredPropertiesException exception if any of the required properties are not resolved.
void validateRequiredProperties() throws MissingRequiredPropertiesException;

Rewritten in the abstract subclass AbstractProperty Resolver

@Override
public void validateRequiredProperties() {
  // Property cannot find the object that throws an exception
  MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
  for (String key : this.requiredProperties) {
    if (this.getProperty(key) == null) {
      ex.addMissingRequiredProperty(key);
    }
  }
  if (!ex.getMissingRequiredProperties().isEmpty()) {
    throw ex;
  }
}

Because in our source code analysis, we don't see any operations that are adding operations to requiredProperties, which is as follows:

@Override
public void setRequiredProperties(String... requiredProperties) {
  if (requiredProperties != null) {
    for (String key : requiredProperties) {
      this.requiredProperties.add(key);
    }
  }
}

So the set set set of requiredProperties is null, and there is no element without parsing.

This is the end of this article. The next step in source analysis is to create IOC containers and parse beans.

Topics: PHP xml Attribute Spring jvm