Spring Bean assembly process is an old topic, from the beginning of the assembly of beans in XML form, to the later assembly of beans using annotations and development using SpringBoot and SpringCloud, Spring has evolved and improved throughout the process. Whether it’s the original Spring, SpringBoot, or SpringCloud, they’re all based on Spring, maybe some encapsulation of Spring, but they’re still Spring.

There isn’t that much automation in the first place, but Spring hides all the implementation details inside the framework. Understanding SpringBoot or SpringCloud is only natural if you really understand Spring.

start

Based on Spring version 5.2.5

Create a Maven project and import the following JAR packages

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5. RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-beans</artifactId>
  <version>5.2.5. RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.2.5. RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.5. RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.5. RELEASE</version>
</dependency>
Copy the code

Create the applicationContext. XML file in the classpath path and configure a new bean


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
    <bean id="user" class="com.liqiwen.spring.bean.User">
        <property name="id" value="23" />
        <property name="name" value="zhangsan" />
    </bean>
</beans>
Copy the code

After the addition, the schematic diagram of the project is as follows

Load beans as XML

An example of simple and basic code for getting a Bean object is as follows:

public static void main(String[] args) {

    // Spring Bean loading process
    Resource resource = new ClassPathResource("applicationContext.xml");
    // Get a BeanFactory
    DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
    // Define Bean defines reader
    BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
    // Read the bean from the resource file
    beanDefinitionReader.loadBeanDefinitions(resource);
	// Get the bean from the factory
    User user = (User) defaultListableBeanFactory.getBean("user");

    System.out.println(user.getId() + "=" + user.getName());
}
Copy the code

Next, let’s examine line by line how beans are loaded and cached by the Spring container.

1. Define resource objects

Resource resource = new ClassPathResource("applicationContext.xml");
Copy the code

Convert the applicationContext. XML file in classpath to a Resource file. Resource is also a Resource interface provided by Spring, and in addition to the ClassPathResource used in our example, Spring also provides other forms of Resource implementation classes.

Go to the new ClassPathResource(” ApplicationContext.xml “) constructor and see what the constructor does.

public ClassPathResource(String path) {
    this(path, (ClassLoader) null);
}
Copy the code

Found that the constructor calls another constructor of its own

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
    Assert.notNull(path, "Path must not be null");
    String pathToUse = StringUtils.cleanPath(path);
    if (pathToUse.startsWith("/")) {
        pathToUse = pathToUse.substring(1);
    }
    this.path = pathToUse;
    // Get a classloader
    this.classLoader = (classLoader ! =null ? classLoader : ClassUtils.getDefaultClassLoader());
}
Copy the code

From this constructor, we know that the constructor initializes a class loader. If the class loader is not empty, it is assigned to the default class loader. If the class loader is empty, by ClassUtils. GetDefaultClassLoader () method to obtain a default class loader. If we pass in a class loader that is obviously null, Spring will automatically get the default class loader.

public static ClassLoader getDefaultClassLoader(a) {
    ClassLoader cl = null;
    try {
    	// 1. Get the class loader for the current thread
        cl = Thread.currentThread().getContextClassLoader();
    }
    catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        // 2. Get the classloader for the current class
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoader
            try {
            	// Get the system-level class loader/application class loader AppClassLoader
                cl = ClassLoader.getSystemClassLoader();
            }
            catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...}}}return cl;
}
Copy the code

Using the getDefaultClassLoader method, we can see that Spring does three things to get the classloader:

  • Gets the class loader for the current thread, or returns it if it exists. If it does not exist, proceed
  • Gets the classloader that loaded the current class, or returns it if it exists. If it does not exist, proceed
  • If no class loader is obtained in the preceding two steps, the system-level class loader/application class loader is obtained.

There is a very good use of a fallback mechanism, and to explain the fallback mechanism in a popular way is to settle for second best. Get the most appropriate XXX first. If not, obtain the next appropriate XX. If you still can’t get it, you take a step back and get x.

2. Initialize the BeanFactory

Let’s look at the implementation of the second line in the sample code

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
Copy the code

Look at the new DefaultListableBeanFactory () method do?

/ * * * Create a default the BeanFactory * Create a new DefaultListableBeanFactory. * /
public DefaultListableBeanFactory(a) {
    super(a); }Copy the code

An empty implementation, but calls the constructor of the superclass. Further, see what the constructor of the superclass does.

public AbstractAutowireCapableBeanFactory(a) {
    super(a); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }Copy the code

Again, the constructor calls the parent constructor to follow up on the parent constructor.

public AbstractBeanFactory(a) {}Copy the code

Empty implementation, nothing to look at. Look at the other three methods AbstractAutowireCapableBeanFactory inside. We can roughly guess from the name of the method that this is to ignore certain dependent interfaces.

private finalSet<Class<? >> ignoredDependencyInterfaces =new HashSet<>();
public void ignoreDependencyInterface(Class
        ifc) {
    this.ignoredDependencyInterfaces.add(ifc);
}
Copy the code

There is not much complicated logic, just putting certain class objects into a set, and marking these interfaces should be ignored, perhaps the set will be used somewhere later. Note, however, that only the BeanFactory interface should be ignored.

3. Define BeanDefinitionReader objects

Let’s look at the implementation of the third line of code

BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
Copy the code

By the bank code, we define a Bean definition of readers, the second step is to generate defaultListableBeanFactory object into the us to define the reader.

/**
 * Create new XmlBeanDefinitionReader for the given bean factory.
 * @param registry the BeanFactory to load bean definitions into,
 * in the form of a BeanDefinitionRegistry
 */
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    super(registry);
}
Copy the code

This method creates a BeanDefinitionReader for the given BeanFactory. Here we can see that the construction method of the parameter type is BeanDefinitionRegistry type, why we define DefaultListableBeanFactory can also introduced to go in? Apparently our DefaultListableBeanFactory implements this interface. Let’s look at the inheritance diagram for DefaultListBeanFactory.

XMLBeanDefinitionReader’s constructor calls the parent class’s constructor, so follow the parent class’s constructor.

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;

    // Determine ResourceLoader to use. Decide which ResourceLoader to use
    // 1. Obtain ResourceLoader according to the BeanDefinitionRegistry passed in
    if (this.registry instanceof ResourceLoader) {
        this.resourceLoader = (ResourceLoader) this.registry;
    } else {
        this.resourceLoader = new PathMatchingResourcePatternResolver();
    }

    // Inherit Environment if possible
    // 2. Get the current environment according to the BeanDefinitionRegistry passed in
    if (this.registry instanceof EnvironmentCapable) {
        this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
    } else {
        this.environment = newStandardEnvironment(); }}Copy the code

This constructor does several things

  • Assign to the Registry type of the current class
  • Get the object’s ResourceLoader based on the parameters passed in
  • Gets the current environment based on the parameters passed in
public PathMatchingResourcePatternResolver() {
    this.resourceLoader = new DefaultResourceLoader();
}
Copy the code

Clearly the BeanDefinitionRegistry passed in is not an implementation class for ResourceLoader, as we can see from the class inheritance diagram. Therefore, the current class ResourceLoader for new PathMatchingResourcePatternResolver (); This method gets the default ResourceLoader.

public PathMatchingResourcePatternResolver(a) {
    this.resourceLoader = new DefaultResourceLoader();
}
public DefaultResourceLoader(a) {
	// Returns a default ResourceLoader and assigns a classLoader to the current class
    this.classLoader = ClassUtils.getDefaultClassLoader();
}
Copy the code

With all the preparation we’ve done, let’s actually load the Bean definition from our defined ApplicationContext.xml. This feature by beanDefinitionReader. LoadBeanDefinitions (resource); To implement.

4. Load the BeanDefinition from the given resource file

See beanDefinitionReader. LoadBeanDefinitions (resource); How the Bean definition is loaded.

Spring is very particular about method names, which are basically what they mean by name. LoadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions loadBeanDefinitions

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}
Copy the code

The main function of this method is to load beanDefinitions from the specified Resource file. The return value of this method is the number of BeanDefinitions returned. Here Spring wraps the incoming Resource object into an EncodedResource object. As the name implies, we know that this object is just a encapsulation of the Resource, which contains the specified Resource resources, but also contains the encoding information, enter the source code of EncodedResource.

public EncodedResource(Resource resource) {
    this(resource, null.null);
}
public EncodedResource(Resource resource, @Nullable String encoding) {
    this(resource, encoding, null);
}
public EncodedResource(Resource resource, @Nullable Charset charset) {
    this(resource, null, charset);
}
/** * private constructor **/
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
    super(a); Assert.notNull(resource,"Resource must not be null");
    this.resource = resource;
    this.encoding = encoding;
    this.charset = charset;
}
Copy the code

As you can see, Charset and Encoding are mutually exclusive attributes. Obviously we are only passing in the Resource object, so the default encoding and charset are null.

Let’s look at a concrete implementation of loadBeanDefinitions(EncodedResource E).

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   	/ /... Omit invalid code
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if(! currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        InputSource inputSource = new InputSource(inputStream);
        if(encodedResource.getEncoding() ! =null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove(); }}}Copy the code

ResourcesCurrentlyBeingLoaded is a ThreadLocal object, first to get the current from the resourcesCurrentlyBeingLoaded encodedResource, if get it out of the empty, The initialization of a new HashSet < EncodedResource > object, placing it into the resourcesCurrentlyBeingLoaded object. Then judge whether the object in the set of resourcesCurrentlyBeingLoaded set already exists, if it exists, it throws BeanDefinitionStoreException exception, then the exception can be in when to appear? We can try to modify applicationContext.xml.

<! Import your own configuration file from the import component
<import resource="applicationContext.xml" />

<bean id="user" class="com.liqiwen.spring.bean.User">
    <property name="id" value="23" />
    <property name="name" value="zhangsan" />
</bean>
Copy the code

This obviously creates a circular reference that must throw an exception at this point.

This is a clever way to tell if the definitions in the applicationContext.xml file have been looped in by the fact that the Set cannot have duplicate data.

Take a look at the implementation of doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        int count = registerBeanDefinitions(doc, resource);
        return count;
    }
   / /... Omit some code
}
Copy the code

Apparently doLoadBeanDefinitions did two things

  • Reading the contents of an XML file from the given Resource returns a Document object
  • Register the definition of the bean through the Document object and the given Resource Resource

To read the doLoadDocument method, you actually read the contents of the XML and return a Document object. This part will not follow the source code to see, interested in their own search for XML parsing related content.

Take a look at the registerBeanDefinitions source code to see how you can get the definition registered to the bean from the Document.

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // create a Bean definition document reader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // Get the number of Bean definitions that have been obtained in the factory
    int countBefore = getRegistry().getBeanDefinitionCount();
    // Register the Bean definition
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // Returns the number of bean definitions to register this time
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
Copy the code

See how to obtain a Bean definition document reader (BeanDefinitionDocumentReader)

private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
			DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader(a) {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}
Copy the code

See here a call by the Spring a BeanUtils instantiateClass method. Introduced into DefaultBeanDefinitionDocumentReader class files, thought we can know that the method is generated by means of reflection BeanDefinitionDocumentReader the object instance. The following to BeanUtils. Verify instantiateClass source code.

public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
  Assert.notNull(clazz, "Class must not be null");
  // If an interface is passed in, an exception is thrown
  if (clazz.isInterface()) {
      throw new BeanInstantiationException(clazz, "Specified class is an interface");
  }
  try {
      // Instantiate the class to get the constructor
      return instantiateClass(clazz.getDeclaredConstructor());
  }
  catch (NoSuchMethodException ex) {
      // Support for Kotlin
      Constructor<T> ctor = findPrimaryConstructor(clazz);
      if(ctor ! =null) {
          return instantiateClass(ctor);
      }
      throw new BeanInstantiationException(clazz, "No default constructor found", ex);
  }
  catch (LinkageError err) {
      throw new BeanInstantiationException(clazz, "Unresolvable class definition", err); }}Copy the code

Look at the overloaded instantiateClass method, it’s all reflection related.

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    Assert.notNull(ctor, "Constructor must not be null");
    try {
    	// Set makeAccessible to true,
        ReflectionUtils.makeAccessible(ctor);
        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
            return KotlinDelegate.instantiateClass(ctor, args);
        }
        else{ Class<? >[] parameterTypes = ctor.getParameterTypes(); Assert.isTrue(args.length <= parameterTypes.length,"Can't specify more arguments than constructor parameters");
            Object[] argsWithDefaultValues = new Object[args.length];
            for (int i = 0 ; i < args.length; i++) {
                if (args[i] == null) { Class<? > parameterType = parameterTypes[i]; argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) :null);
                }
                else{ argsWithDefaultValues[i] = args[i]; }}returnctor.newInstance(argsWithDefaultValues); }}/ /... Omit some code
}
Copy the code

Note in the head of the method call ReflectionUtils makeAccessible (ctor); Method, which shows that even if you offer a private constructor, Spring also can help you to create the object (reflection), and see the final return, obviously BeanUtils. InstantiateClass object is generated by means of reflection.

  • If no constructor is provided, the default constructor is used
  • If a private constructor is provided, the accessible property is set to true and reflection is called to generate an instance of the object

As you can see from the above, Spring does everything it can to help us create an object properly. Look at the incoming DefaultBeanDefinitionDocumentReader statement

If you look at these red boxes, you’ll feel familiar with the tags we defined in ApplicationContext.xml. The original and all these things be DefaultBeanDefinitionDocumentReader write to death in the code.

Let’s go back to the implementation of registerBeanDefinitions.

We already know createBeanDefinitionDocumentReader is generated by means of reflection a BeanDefinitionDocumentReader object. Now let’s look at what the second line of the method does.

getRegistry().getBeanDefinitionCount(); Take a look at the getRegistry() method first. Don’t have to consider this method basically, affirmation is to get introduced to come in to our defaultListableBeanFactory object.

private final BeanDefinitionRegistry registry;
@Override
public final BeanDefinitionRegistry getRegistry(a) {
    return this.registry;
}
Copy the code

The member variable Registry is returned. Where is registry assigned? Look at step 3 in our sample code, new XMLBeanDefinitionReader(). In the constructor of this class, we assign the value of the member variable Registry.

Then look at the implementation of getBeanDefinitionCount

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public int getBeanDefinitionCount(a) {
    return this.beanDefinitionMap.size();
}
Copy the code

Return beanDefinitionMap the size of concurrentHashMap. This variable is defined as Map

, which is a Map object with bean name as key and BeanDefinition as value.
,>

Int countBefore = getRegistry().getBeandefinitionCount (); What is actually returned is the number of BeanDefinitions before they were loaded.

Then look at the third line implementation of registerBeanDefinitions. documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); Start registering the bean definition from the document through the document reader. Let’s look at the implementation

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    //doc.getDocumentElement() gets the Element in the document
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested 
      
        tags will cause recursion in this method
      
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    / /... Omit some code
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}
Copy the code

We know from the comments in your doRegisterBeanDefinitions,

  • This method may cause recursion if we configure references to other in applicationContext.xml<beans>
  • This method uses a typical delegate. I want to do something myself, I don’t do it myself, let other classes do it for me.

Look at the method’s preProcessXml(root), which is an empty implementation, and then look at postProcessXml(root), which is also an empty implementation

This is a typical template approach design pattern. As you can see, this method is protected and left to subclasses to implement. The core parsing logic is in the parseBeanDefinitions method.

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
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)) {
                    parseDefaultElement(ele, delegate);
                }
                else{ delegate.parseCustomElement(ele); }}}}else{ delegate.parseCustomElement(root); }}Copy the code

This method resolves the topmost tag elements of the document, such as beans, imports, and so on, as well as custom tag elements in addition to spring-specified tag nodes. Custom tags are rarely used, so take a look at parsing the default tag element. Look at parseDefaultElement.

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // IMPORT_ELEMENT = "import"
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // ALIAS_ELEMENT = "alias"
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // BEAN_ELEMENT = "bean"
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // NESTED_BEANS_ELEMENT = "beans"
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        / / recurse recursiondoRegisterBeanDefinitions(ele); }}Copy the code

Before we talk about doRegisterBeanDefinitions method will lead to a recursive, has been proved in the last line of the method. If there is a

tag defined (nested beans)

As an example, back in the loadBeanDefinitions method, Spring uses a Set to detect the presence of a circular import configuration file. If a circular import is present, Spring throws an exception on loadBeanDefinitions. This appears inevitable for a reason, we like to see the Spring in the importBeanDefinitionResource is how to deal with the import this kind of label.

protected void importBeanDefinitionResource(Element ele) {
    // Get the resource attribute in the element
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    if(! StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele);
        return;
    }

    // Resolve system properties: e.g. "${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<>(4);

    // Discover whether the location is an absolute or relative URI
    boolean absoluteLocation = false;
    try {
        // Check whether the resource value is an absolute path
        absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    }
    catch (URISyntaxException ex) {
        // cannot convert to an URI, considering the location relative
        // unless it is the well-known Spring prefix "classpath*:"
    }

    // Absolute or relative?
    if (absoluteLocation) { // Absolute path
        try {
            // The loadBeanDefinitions method is called
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isTraceEnabled()) {
                logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]"); }}catch (BeanDefinitionStoreException ex) {
            getReaderContext().error(
                    "Failed to import bean definitions from URL location [" + location + "]", ele, ex); }}else { // Relative path
        // No URL -> considering resource location as relative to the current file.
        try {
            int importCount;
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
            // The loadBeanDefinitions method is called
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
                String baseLocation = getReaderContext().getResource().getURL().toString();
                // The loadBeanDefinitions method is calledimportCount = getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location), actualResources); }}catch (IOException ex) {
            getReaderContext().error("Failed to resolve current resource location", ele, ex);
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error(
                    "Failed to import bean definitions from relative location [" + location + "]", ele, ex);
        }
    }
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

Copy the code

LoadBeanDefinitions is called in both branches, regardless of whether the resource attribute of the import tag is configured with an absolute or relative path. This causes Spring to parse the import tag to determine whether to import a reference to the loop’s XML file, and also to verify that Spring will throw an exception if the loop imports.

We don’t care about processing the alias tag, which is rare in practice, so we’ll skip it here. Look directly at the implementation of the processBeanDefinition method.

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if(bdHolder ! =null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            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(newBeanComponentDefinition(bdHolder)); }}Copy the code

Is the main function of the method from the given Bean Element tag parsing out BeanDefinition and puts it into the registry, given our DefaultListableBeanFactory statement. Look at the delegate. How parseBeanDefinitionElement parsing.

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
	// Get the id attribute from the tag
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // Get the name attribute from the tag
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    List<String> aliases = new ArrayList<>();
    // Alias processing
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }
    // Use id as the bean name
    String beanName = id;
    // If beanName is empty, the first element in the array of aliases is fetched as beanName
    if(! StringUtils.hasText(beanName) && ! aliases.isEmpty()) { beanName = aliases.remove(0);
    }
    // Check the uniqueness of the name
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    // Parse out the bean definition
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if(beanDefinition ! =null) {
        // If the resolved Bean has no beanName, a name is automatically generated for the Bean
        if(! StringUtils.hasText(beanName)) {try {
                if(containingBean ! =null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // Register an alias for the plain bean class name, if still possible,
                    // if the generator returned the class name plus a suffix.
                    // This is expected for Spring 1.2/2.0 backwards.
                    String beanClassName = beanDefinition.getBeanClassName();
                    if(beanClassName ! =null &&
                            beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                            !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); }}if (logger.isTraceEnabled()) {
                    logger.trace("Neither XML 'id' nor 'name' specified - " +
                            "using generated bean name [" + beanName + "]"); }}catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        // Returns a BeanDefinitionHolder object
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}
Copy the code

This method does several things

  • Gets the ID and alias attributes, as well as the class attribute, and, if no name exists, the first element of the alias as the bean name
  • Parse a BeanDefinition and return an AbstractBeanDefinition object
  • Check whether AbstractBeanDefinition contains a bean name and, if not, generate a bean name for the bean
  • Returns a wrapped BeanDefinitionHolder object that contains all the attributes of the bean configured in THE XML, along with an array of bean names and aliases.

Obviously, the focus is on step 2, how to return an AbstractBeanDefinition object. See parseBeanDefinitionElement implementation of this method.

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    // Whether there is a class attribute
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    // Whether there is a parent attribute
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {
        // Create a bean definition
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        // Resolve beanDefinitionAttributes, including init-method, destroy-method, etc
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        
        // Parse the construction parameters
        parseConstructorArgElements(ele, bd);
        // Parse the attribute parameters
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    // omit some code...

    return null;
}

Copy the code

How do you create a BeanDefinition

public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {

    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setParentName(parentName);
    if(className ! =null) {
        if(classLoader ! =null) {
            bd.setBeanClass(ClassUtils.forName(className, classLoader));
        }
        else{ bd.setBeanClassName(className); }}return bd;
}
Copy the code

First we get a GenericBeanDefinition object from new, and then we determine whether we should give it a class object or a className based on whether there is a classLoader object. And finally, GenericBeanDefinition returns.

ParseBeanDefinitionAttributes method source code is as follows:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
			@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
            
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { If you set the singleton property, spring will prompt you to upgrade it to scope
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }
    else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // Does the scope attribute exist
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }
    else if(containingBean ! =null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {// Whether there is an abstract attribute
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); // Whether there is a lazy-init attribute
    if (isDefaultValue(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); // Whether there is an autowiring attribute
    bd.setAutowireMode(getAutowireMode(autowire));

    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { // Whether there is a depends-on attribute
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }

    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if (isDefaultValue(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if(candidatePattern ! =null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); }}else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }

    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }

    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        bd.setInitMethodName(initMethodName);
    }
    else if (this.defaults.getInitMethod() ! =null) {
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }

    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        bd.setDestroyMethodName(destroyMethodName);
    }
    else if (this.defaults.getDestroyMethod() ! =null) {
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }

    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }

    return bd;
}
Copy the code

It is simply a matter of parsing the other properties in the bean tag and assigning the properties of the BeanDefinition object returned by createBeanDefinition. In case you’re wondering, we didn’t configure any other properties in the bean tag, but some properties still have default values. The attribute definition here corresponds to the bean tag in applicationContext.xml.

There are two other methods that we need to look at

/** * Parse constructor-arg sub-elements of the given bean element
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if(isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { parseConstructorArgElement((Element) node, bd); }}}/** * Parse property sub-elements of the given bean element
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
    NodeList nl = beanEle.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
           // This contains the processing of value and refparsePropertyElement((Element) node, bd); }}}Copy the code

At this point, we have returned a full BeanDefinitionHolder object.

This BeanDefinitionHolder contains BeanDefinition objects and beanName attributes parsed from the XML file.

BdHolder = delegate. DecorateBeanDefinitionIfRequired (ele, bdHolder) BeanDefinitionHolder decorate it is returned to us, That is, see if any additional attributes need to be added, and return a BeanDefinitionHolder object.

The last key to, through the BeanDefinitionReaderUtils registerBeanDefinition method to register a BeanDefinitionHolder objects in the registry, and see how to register.

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    // That's the point
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if(aliases ! =null) {
        for(String alias : aliases) { registry.registerAlias(beanName, alias); }}}Copy the code

The key is that we call the registry registerBeanDefinition method, registerBeanDefinition have multiple implementations, and obviously we should view the DefaultListableBeanFactory implementation.

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    // Check whether BeanDefinition is an instance of AbstractBeanDefinition. I'm just checking beanDefinition here
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex); }}Get the beanDefinitionMap from beanDefinitionMap
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if(existingDefinition ! =null) {
        // Determine if there is a bean with the same name
        if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            / / empty implementation
        } else if(! beanDefinition.equals(existingDefinition)) {/ / empty implementation
        } else {
            / / empty implementation
        }
        // Put it back into concurrentHashMap
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        if (hasBeanCreationStarted()) { // Check that the factory bean creation phase has already started
            synchronized (this.beanDefinitionMap) { // beanDefinitionMap concurrentHashMap was synchronized here to prevent concurrency if the bean is not registered.
                // Place the beanDefinition in the cache
                this.beanDefinitionMap.put(beanName, beanDefinition);
                // Define an updated collection
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                // Place all beanDefinitionNames in updatedDefinitions
                updatedDefinitions.addAll(this.beanDefinitionNames);
                // Place the beanName to be added into updatedDefinitions
                updatedDefinitions.add(beanName);
                // Re-assign beanDefinitionNames
                this.beanDefinitionNames = updatedDefinitions; removeManualSingletonName(beanName); }}else {
            // Still in the startup registration phase
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }
	
    // Reset BeanDefinition's cache
    if(existingDefinition ! =null|| containsSingleton(beanName)) { resetBeanDefinition(beanName); }}Copy the code

At this point, all our bean objects defined in XML have been parsed out. All beans are stored in A beanDefinitionMap in Registry, which is a concurrentHashMap, The key is beanName, the value is all the definition of the bean, including className/class, scope, init-method… And so on. At this point, the entire bean loading process is complete. Note, however, that our bean has not been created at this point. So when was the bean created?

The Bean creation process

Spring loaded the bean definition from the applicationContext. XML file and stored it in a beanDefinitionMap. Our bean object was not initialized.

Take a look at defaultListableBeanFactory getBean method. Let’s see how that works. Spring’s BeanFactory provides a variety of getBean methods. But their essence is to call the doGetBean method. Let’s go straight to what the doGetBean method does.

Take a look at the source code implementation of doGetBean (there is a lot of source code for doGetBean, but some useless logging logic has been removed because there is too much source code)

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // Convert the beanName of the bean
  final String beanName = transformedBeanName(name);
  Object bean; // This is defined as an Object Object, because Spring doesn't know the type of Object we want to get, and uses the Object Object to receive it

  // Check to see if such beans already exist in the Spring container
  Object sharedInstance = getSingleton(beanName); SharedInstance = scope = singleton; sharedInstance = scope = scope
  if(sharedInstance ! =null && args == null) {
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  } else {
     
      if (isPrototypeCurrentlyInCreation(beanName)) {
          throw new BeanCurrentlyInCreationException(beanName);
      }

      // Check whether beanDefinition already exists. Currently null is returned based on parentBeanFactory
      BeanFactory parentBeanFactory = getParentBeanFactory();
      if(parentBeanFactory ! =null && !containsBeanDefinition(beanName)) {
          // Not found -> check parent.
          String nameToLookup = originalBeanName(name);
          if (parentBeanFactory instanceof AbstractBeanFactory) {
              return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                      nameToLookup, requiredType, args, typeCheckOnly);
          } else if(args ! =null) {
              // Delegation to parent with explicit args.
              return (T) parentBeanFactory.getBean(nameToLookup, args);
          } else if(requiredType ! =null) {
              // No args -> delegate to standard getBean method.
              return parentBeanFactory.getBean(nameToLookup, requiredType);
          } else {
              return(T) parentBeanFactory.getBean(nameToLookup); }}// Marks the current object as already created, in effect adding the beanName to the Set
      if(! typeCheckOnly) { markBeanAsCreated(beanName); }try {
      	  // Convert BeanDefinitionMap beanDefinitionDefinition to RootBeanDefinition
          final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
          checkMergedBeanDefinition(mbd, beanName, args);

          // Get the dependency information for the merged bean
          String[] dependsOn = mbd.getDependsOn();
          if(dependsOn ! =null) {
              for (String dep : dependsOn) {
                  if (isDependent(beanName, dep)) {
                      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                              "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                  }
                  registerDependentBean(dep, beanName);
                  try {
                      getBean(dep);
                  }
                  catch (NoSuchBeanDefinitionException ex) {
                      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                              "'" + beanName + "' depends on missing bean '" + dep + "'", ex); }}}// Create an instance of the bean
          if (mbd.isSingleton()) {
              sharedInstance = getSingleton(beanName, () -> {
                  try {
                      return createBean(beanName, mbd, args);
                  }
                  catch (BeansException ex) {
                      // Explicitly remove instance from singleton cache: It might have been put there
                      // eagerly by the creation process, to allow for circular reference resolution.
                      // Also remove any beans that received a temporary reference to the bean.
                      destroySingleton(beanName);
                      throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }else if (mbd.isPrototype()) {
              // It's a prototype -> create a new instance.
              Object prototypeInstance = null;
              try {
                  beforePrototypeCreation(beanName);
                  prototypeInstance = createBean(beanName, mbd, args);
              }
              finally {
                  afterPrototypeCreation(beanName);
              }
              bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
          } else {
              String scopeName = mbd.getScope();
              final Scope scope = this.scopes.get(scopeName);
              if (scope == null) {
                  throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
              }
              try {
                  Object scopedInstance = scope.get(beanName, () -> {
                      beforePrototypeCreation(beanName);
                      try {
                          return createBean(beanName, mbd, args);
                      }
                      finally{ afterPrototypeCreation(beanName); }}); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); }catch (IllegalStateException ex) {
                  throw new BeanCreationException(beanName,
                          "Scope '" + scopeName + "' is not active for the current thread; consider " +
                          "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); }}}catch (BeansException ex) {
          cleanupAfterBeanCreationFailure(beanName);
          throwex; }}// Check if required type matches the type of the actual bean instance.
  if(requiredType ! =null && !requiredType.isInstance(bean)) {
      try {
          T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
          if (convertedBean == null) {
              throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
          }
          return convertedBean;
      }
      catch (TypeMismatchException ex) {
          if (logger.isTraceEnabled()) {
              logger.trace("Failed to convert bean '" + name + "' to required type '" +
                      ClassUtils.getQualifiedName(requiredType) + "'", ex);
          }
          throw newBeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); }}return (T) bean;
}
Copy the code

The above code does a few things

1. Check whether the Bean object already exists in the singleton cache based on the beanName

So how does the inspection work? You can find out with getSingleton.

// The allowEarlyReference passed in is true

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // Use singletonObjects as a synchronized block to prevent concurrency
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null&& allowEarlyReference) { ObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
                if(singletonFactory ! =null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

You can see that the singletonObject is obtained from the current singletonObjects object, which is a ConcurrentHashMap object, Used to cache beans after the SpringIOC container has been initialized. And only beans with scope attributes as singletons are cached, not beans with prototype attributes.

If the cache object is empty and the current object is being created, the problem of circular references begins to be dealt with. If the currently cached object is not empty, the current cached singletonObject is returned.

This involves a circular reference problem, which will be explained in a separate article. Here we focus on the bean creation process.

Obviously we’re getting it for the first time, so singletonObjects ConcurrentHashMap does not have an instance of this object.