Spring learning -- initialization process of IOC container

Posted by SyndicatE on Tue, 04 Jan 2022 04:25:28 +0100

IOC container initialization overview

 

IOC container initialization is started by the refresh() method, which marks the official start of IOC container. Spring separates the IOC container startup process and uses different modules to complete it, such as ResourceLoader, BeanDefinition and other modules. The IOC container startup mainly includes three processes:

  • Resource location process:

Resource positioning refers to the resource positioning of beandefinition, which is completed by ResourceLoader through a unified resource interface. This resource provides a unified interface for the use of various forms of beandefinition. This process is similar to the process of the container looking for data.

  • Loading of BeanDefinition:

The loading process of BeanDefinition refers to representing the user-defined beans as the data structure inside the container. The data structure of the container is actually BeanDefinition. In fact, BeanDefinition is the abstraction of POJO objects in the container. The data structure defined by BeanDefinition is like the abstraction of everything in the world in java, and the abstraction of java objects in the container is BeanDefinition.

  • Register BeanDefinition with container:

This registration process is completed by calling the BeanDefinitionRegistry interface, which is to register the BeanDefinition parsed during loading with the IOC container. We can get from the following source code that the registration process is to inject BeanDefinition into a HashMap in the IOC container, and the IOC container holds BeanDefinition data through this HashMap.

Write the initialization of an IOC container by yourself

//Create the abstract resource of IOC configuration file, which contains the definition information of BeanDefinition
ClassPathResource resource = new ClassPathResource("bean.xm");

//Create a bean factory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

//Create a reader, where XmlBeanDefinitionReader is used to load the Bean factory in the form of XML file
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

//Xmlbean factory uses reader to read configuration information
reader.loadBeanDefinitions(resource);

1. Resource positioning of BeanDefinition

When using the defaultlistablebeanfactory programmatically above, you first need to define a Resource to locate the container BeanDefinition. Defaultlistablebeanfactory cannot directly use resources. It needs a BeanDefinition to process these information. In ApplicationContext, Spring has provided the implementation of a series of different Resource readers.

Common Resource types are as follows:

  • FileSystemResource: access resources in the absolute path of a File, and the effect is similar to that of File in Java;
  • Classpathresource: access resources by classpath. The effect is similar to this getClass(). getResource("/"). getPath();
  • ServletContextResource: web application accesses resources in the way of root directory. The effect is similar to request getServletContext(). getRealPath("");
  • UrlResource: implementation class for accessing network resources. For example, resource objects with prefixes such as file: http: ftp:;
  • ByteArrayResource: implementation class for accessing byte array resources

Let's specifically describe the positioning process of BeanDefinition taking FileSystemXmlApplicationContext as an example.

1. Class relationship of FileSystemXmlApplicationContext

This is a graph:

 

          

From the above inheritance relationship, FileSystemXmlApplicationContext inherits from AbstractApplicationContext. The base class of AbstractApplicationContext is DefaultResourceLoader, which inherits the capabilities of ResourceLoader.

First, let's take a look at the specific implementation of FileSystemXmlApplicationContext

	public FileSystemXmlApplicationContext() {
	}


	public FileSystemXmlApplicationContext(ApplicationContext parent) {
		super(parent);
	}


	public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}


	public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
		this(configLocations, true, null);
	}


	public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
		this(configLocations, true, parent);
	}


	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
		this(configLocations, refresh, null);
	}


	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

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



	@Override
	protected Resource getResourceByPath(String path) {
		if (path != null && path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}

The refresh method is invoked in the construction method. This method marks the initialization of the IOC container. We point in and go to the base class AbstractApplicationContext of FileSystemXmlApplicationContext.

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 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);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// 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();
			}
		}
	}

This Refresh method is actually related to the Bean life cycle. I haven't looked at the Bean declaration cycle here. I'll explain it later. Here, focus on the obtainFreshbeanFactory() method, which is the entry for IOC container initialization.

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

The refreshBeanFactory method is invoked in the obtainFreshBeanFactory method. This method is abstract. The concrete implementation is based on the specific conditions of its subclasses. As we are now looking at the FileSystemXmlApplicationContext process, we enter the refreshBeanFactory method in the AbstractFreshableApplicationContext class.

	@Override
	protected final void refreshBeanFactory() throws BeansException {
    //Here, you can judge whether there is a BeanFactory. If there is, destroy and close the BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
    //Here to create a new Beanfactory, the DefaultListableBeanFactory is created
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
     //Load Bean
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

Continue to click into the loadbean definition method, which has many overloads.

The loadBeanDefinition method clicked in is also an abstract method, which needs to be viewed in the implementation class. We still choose the AbstractXmlApplicationContext class related to FileSystemXmlApplicationContext

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //Create a configuration reader
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        //Set relevant properties of BeanDefinition
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        //Incoming reader
		initBeanDefinitionReader(beanDefinitionReader);
		//Load get BeanDefinition location
        loadBeanDefinitions(beanDefinitionReader);
	}

Continue clicking in the loadBeanDefinition method

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //Initialization will not go one way because there is no Resource object yet
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
        //Get as a String file
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

Click in the second loadBeanDefinition method.

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        String[] var3 = locations;
        int var4 = locations.length;
        //Loop load profile
        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];
            counter += this.loadBeanDefinitions(location);
        }

The loadBeanDefinition method continues to click in to view the loading process

 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(location, (Set)null);
    }

    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        } else {
            int loadCount;
            if (!(resourceLoader instanceof ResourcePatternResolver)) {
                Resource resource = resourceLoader.getResource(location);
                loadCount = this.loadBeanDefinitions((Resource)resource);
                if (actualResources != null) {
                    actualResources.add(resource);
                }

                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
                }

                return loadCount;
            } else {
                try {
                    //Get the specific location of the Resource
                    Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                    loadCount = this.loadBeanDefinitions(resources);
                    if (actualResources != null) {
                        Resource[] var6 = resources;
                        int var7 = resources.length;

                        for(int var8 = 0; var8 < var7; ++var8) {
                            Resource resource = var6[var8];
                            actualResources.add(resource);
                        }
                    }

                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                    }

                    return loadCount;
                } catch (IOException var10) {
                    throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
                }
            }
        }
    }

Take a direct look at the getResource method of DefaultResourceLoader

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        Iterator var2 = this.protocolResolvers.iterator();

        Resource resource;
        do {
            if (!var2.hasNext()) {
                if (location.startsWith("/")) {
                    return this.getResourceByPath(location);
                }

                if (location.startsWith("classpath:")) {
                    return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
                }

                try {
                    URL url = new URL(location);
                    return new UrlResource(url);
                } catch (MalformedURLException var5) {
                    return this.getResourceByPath(location);
                }
            }

            ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
            resource = protocolResolver.resolve(location, this);
        } while(resource == null);

        return resource;
    }

If the Resource is not a Url or classpath Resource, call the getResourceByPath method of FileSystemXmlApplicationContext to return a FileSystemResource and locate the Resource. The next step is to load BeanDefinition

2. Loading and parsing of BeanDefinition

3. Registration of BeanDefinition in IOC container

Topics: Java Spring