Initial experience of IoC container based on Spring core principles

This article is excerpted from Spring 5 core principles

1 basic concepts of IOC and DI

IoC (Inversion of Control) is to create, rely on and reverse the objects to be implemented in the original code to the container for implementation. We need to create a container and a description to let the container know the relationship between the object to be created and the object. The most specific expression of this description is the configuration file we see.

DI (Dependency Injection) means that an object passively accepts dependent classes instead of actively looking for them. In other words, it means that an object does not look for its dependent classes from the container, but actively injects its dependent classes into it when the container instantiates an object.
Let's consider it from the perspective of our own design.
(1) How to represent the relationship between objects?
It can be represented by semantic configuration files such as XML and properties.
(2) Where are the files describing object relationships stored?
It may be classpath, filesystem, URL, network resource, servletContext, etc.
(3) Different configuration files have different descriptions of objects, such as standard and user-defined declarative. How to unify them?
Internally, there needs to be a unified definition of objects, and all external descriptions must be transformed into unified descriptions and definitions.
(4) How to parse different configuration files?
You need to use different parsers for different configuration file syntax.

2 spring core container class diagram

2.1. BeanFactory

The creation of beans in Spring is a typical factory mode. This series of Bean factories, namely IoC containers, provide a lot of convenience and basic services for developers to manage the dependencies between objects. In Spring, there are many implementations of IoC containers for users to choose from. Their relationship is shown in the following figure.

As the top interface class, BeanFactory defines the basic functional specifications of IoC container. BeanFactory has three important subclasses: ListableBeanFactory, hierarchalbeanfactory and AutowireCapableBeanFactory. However, from the class diagram, we can find that the final default implementation class is DefaultListableBeanFactory, which implements all interfaces. So why define so many levels of interfaces? Referring to the source code and description of these interfaces, it is found that each interface has its application occasions, mainly to distinguish the transfer and transformation of objects and the restrictions on object data access in the internal operation process of Spring. For example, the ListableBeanFactory interface indicates that these beans can be listed, while the hierarchical BeanFactory indicates that these beans are inherited, that is, each Bean may have a parent Bean. The AutowireCapableBeanFactory interface defines the automatic assembly rules for beans. These three interfaces jointly define the collection of beans, the relationship between beans and Bean behavior. The most basic IoC container interface is BeanFactory. Take a look at its source code:

public interface BeanFactory {
 
   //The escape definition of FactoryBean, because if the name of the Bean is used to retrieve the FactoryBean, the object obtained is the object generated by the factory
   //If you need to get the factory itself, you need to escape
   String FACTORY_BEAN_PREFIX = "&";

   //Obtain the Bean instance obtained in the IoC container according to the Bean name
   Object getBean(String name) throws BeansException;

   //The Bean instance is obtained according to the Bean name and Class type, and the type safety verification mechanism is added
   <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
  
   Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

   //Provide the retrieval of beans to see if there is a Bean with this name in the IoC container
   boolean containsBean(String name);
  
   //Get the Bean instance according to the Bean name, and judge whether the Bean is a singleton
   boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
   boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
   boolean isTypeMatch(String name, ResolvableType typeToMatch) throws
 NoSuchBeanDefinitionException;
   boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws
 NoSuchBeanDefinitionException;
  
   //Get the Class type of the Bean instance
   @Nullable
   Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    //Get the alias of the Bean. If the alias is retrieved, its original name will also be retrieved
   String[] getAliases(String name);

}

BeanFactory only defines the basic behavior of IoC container, and doesn't care how your Bean is defined and loaded. Just as we only care about what products we can get from the factory, we don't care about how the factory produces these products.
To know how the factory generates objects, we need to look at the specific IoC container implementation. Spring provides many IoC container implementations, such as GenericApplicationContext, ClasspathXmlApplicationContext, etc.
ApplicationContext is an advanced IoC container provided by Spring. In addition to providing the basic functions of IoC container, it also provides users with the following additional services.

(1) Support information source and internationalization (implement MessageSource interface).
(2) Access resources (implement the ResourcePatternResolver interface, which will be discussed in later chapters).
(3) Support application events (implement the ApplicationEventPublisher interface).

2.2. BeanDefinition

BeanDefinition is used to save the relevant information of a Bean, including attributes, construction method parameters, dependent Bean name, whether to instantiate or not, delayed loading, etc. it is equivalent to the raw materials of instantiating a Bean. Spring instantiates a Bean according to the information in BeanDefinition., Its inheritance system is shown in the figure below.

2.3. BeanDefinitionReader

The parsing process of bean is very complex, and the functions are divided into very fine parts, because there are many places that need to be extended here, and sufficient flexibility must be guaranteed to deal with possible changes. Bean parsing is mainly used to parse Spring configuration files. This parsing process is mainly completed by BeanDefinitionReader. Take a look at the class structure diagram of BeanDefinitionReader in Spring, as shown in the following figure.

Through the previous analysis, we have a basic macro understanding of the Spring framework system. I hope "friends" can understand it well and form a picture in their mind to lay a good foundation for future learning.

3 initial experience of IoC container based on Web

Let's start with the most familiar DispatcherServlet. The first thing we think of should be the init() method of DispatcherServlet. We did not find the init() method in the DispatherServlet. After exploration, we found it in its parent HttpServletBean. The code is as follows:

@Override
public final void init() throws ServletException {
   if (logger.isDebugEnabled()) {
      logger.debug("Initializing servlet '" + getServletName() + "'");
   }

   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
 this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         //Locate resources
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         //Load configuration information
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, 
getEnvironment()));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }

   initServletBean();

   if (logger.isDebugEnabled()) {
      logger.debug("Servlet '" + getServletName() + "' configured successfully");
   }
}

In the init() method, the code that actually completes the action of initializing the container is actually in the initServletBean() method. We continue to follow up:

@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
   if (this.logger.isInfoEnabled()) {
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }
   catch (RuntimeException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (this.logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed 
            in " + elapsedTime + " ms");
   }
}

In the above code, I finally saw the deja vu code initWebApplicationContext(). Continue to follow up:

protected WebApplicationContext initWebApplicationContext() {

   //Get the parent container WebApplicationContext from ServletContext first
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   //Declare child container
   WebApplicationContext wac = null;

   //Establish the association relationship between parent and child containers
   if (this.webApplicationContext != null) {
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            if (cwac.getParent() == null) {
               cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   //First go to the ServletContext to find out whether the reference of the Web container exists, and create a default empty IoC container
   if (wac == null) {
      wac = findWebApplicationContext();
   }
   //Assign a value to the IoC container created in the previous step
   if (wac == null) {
      wac = createWebApplicationContext(rootContext);
   }
   //Trigger onRefresh() method
   if (!this.refreshEventReceived) {
      onRefresh(wac);
   }

   if (this.publishContext) {
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
      if (this.logger.isDebugEnabled()) {
         this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
               "' as ServletContext attribute with name [" + attrName + "]");
      }
   }

   return wac;
}

@Nullable
protected WebApplicationContext findWebApplicationContext() {
   String attrName = getContextAttribute();
   if (attrName == null) {
      return null;
   }
   WebApplicationContext wac =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
   if (wac == null) {
      throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
   }
   return wac;
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   Class<?> contextClass = getContextClass();
   if (this.logger.isDebugEnabled()) {
      this.logger.debug("Servlet with name '" + getServletName() +
            "' will try to create custom WebApplicationContext context of class '" +
            contextClass.getName() + "'" + ", using parent context [" + parent + "]");
   }
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(),
 getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   wac.refresh();
}

As you can see from the above code, the refresh() method is called in the configAndRefreshWebApplicationContext() method, which is the entrance to start the IoC container. After the IoC container is initialized, the onRefresh() method of DispatcherServlet is invoked. In the onRefresh() method, the initStrategies() method is called directly to initialize nine components of Spring MVC:

@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

//Initialization policy
protected void initStrategies(ApplicationContext context) {
   //Multi file upload component
   initMultipartResolver(context);
   //Initialize locales
   initLocaleResolver(context);
   //Initialize template processor
   initThemeResolver(context);
   //Initialize handlerMapping
   initHandlerMappings(context);
   //Initialize parameter adapter
   initHandlerAdapters(context);
   //Initialize exception interceptor
   initHandlerExceptionResolvers(context);
   //Initialize view preprocessor
   initRequestToViewNameTranslator(context);
   //Initialize view converter
   initViewResolvers(context);
   //Initialize Flashmap Manager
   initFlashMapManager(context);
}

Pay attention to WeChat official account Tom bomb structure and reply to "Spring" to get the complete source code.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness! If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!

It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!

Keywords: Java Spring source code

Added by realmxofxnoise on Sat, 25 Dec 2021 09:03:04 +0200