Principle analysis of Spring core components

Keywords: Java Spring Back-end


This article was not originally created by the blogger. When the blogger learned the underlying principles of Spring, he felt that this article was very profitable. He read it several times and had different experiences each time, so he wanted to record it through the blog.
If there is infringement, it shall be deleted.

The core of Spring

Any actual application consists of many components. Each component is responsible for a part of the whole application function. These components need to coordinate with other application elements to complete their own tasks. When the application runs, these components need to be created and introduced in some way.

There are more than a dozen components in the Spring Framework, but there are only three core components: Spring Core, Spring Context and Spring Bean, which lay the foundation of Spring and support the framework structure of Spring. Other functions and features of Spring, such as Web, AOP and JDBC, are developed and implemented on its basis.

The most important thing in spring is bean. Spring is actually bean oriented programming. Bean means the same to spring as Object means to OOP. So how do the three Core components work together? If bean is compared to an actor in a performance, the Context is the stage of the performance, and the Core is the props of the performance. As for the performance, it is a series of characteristic functions of spring.

We know that beans wrap objects, and there must be data in objects. Context is to provide a living environment for these data, find the relationship between each Bean, and establish and maintain this relationship for them. In this way, context is a collection of Bean relationships, which is what we call the IOC container. So what is the role of core? Core is a series of tools needed to discover, establish and maintain the relationship between each Bean, which is often called Util.

Bean component

Under the org.springframework.beans package of Spring, Bean components mainly complete the creation, definition and parsing of beans.

The creation of SpringBean is a typical factory pattern, and the inheritance hierarchy of its factory is shown in the figure:

Spring uses the factory pattern to manage the objects (beans) used in the program. The top interface of the Bean factory is BeanFactory. In short, the factory returns the corresponding Bean instance as needed.

public interface BeanFactory {
    //...        
    Object getBean(String name);
}

In the factory mode, the Bean generated in the implementation class of the factory is returned to the calling client, which requires the client to provide the factory class that generates its own required class instance, increasing the burden on customers. Spring combines control inversion and dependency injection to provide the required instances for the client, which simplifies the operation of the client. The specific implementation is roughly as follows.

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    /** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>;
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){     
        //...
    }
}

beanDefinitionMap serves as a concrete Bean container in which the object instances created by Spring are saved. When required by the client, use the getBean method of the factory to try to get the corresponding instance. If the instance already exists, return the instance; If the instance does not exist, first generate the corresponding instance and save it in beanDefinitionMap (Lazy Initialization) through registerBeanDefinition method, and then return the instance to the client.

beanDefinitionMap does not directly save the instance itself, but encapsulates the instance in the BeanDefinition object for saving. BeanDefinition contains all the information of the instance, and its simplified version is defined as follows.

public class BeanDefinition {
    private Object bean;

    private Class<?> beanClass;
    private String beanClassName;

    // Initialization value of Bean property field
    private BeanPropertyValues beanPropertyValues;

    //...
}

When the Spring Bean factory produces beans

  1. First save the type parameters of the instance to beanClass and beanClassName, and save the field names and values to be initialized to beanPropertyValues. Spring implements this process through control inversion.
  2. Generate a bean instance, write the field values to be initialized to the bean instance by using the reflection mechanism, save the instance in the bean, and complete the construction of BeanDefinition.

Suppose we have completed the operation in step 1), and the subsequent process is expressed in code as follows

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
    //Generate the bean instance and complete the initialization
    Object bean = createBean(beanDefinition);
    //Save the bean instance in beanDefinition
    beanDefinition.setBean(bean);
    //Save the beanDefinition instance in the Spring container
    beanDefinitionMap.put(beanName, beanDefinition);
}

protected Object createBean(BeanDefinition beanDefinition) {
    try{
        Object bean = beanDefinition.getBeanClass().newInstance();
        try {
            setBeanPropertyValues(bean, beanDefinition);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) {
            e.printStackTrace();
        }
        return bean;
    }catch(InstantiationException e){
        e.printStackTrace();
    }catch(IllegalAccessException e){
        e.printStackTrace();
    }

    return null;
}

protected void setBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
    for(PropertyValue pv : beanDefinition.getBeanPropertyValues().getBeanPropertyValues()){
        Field beanFiled = bean.getClass().getDeclaredField(pv.getName());
        beanFiled.setAccessible(true);
        beanFiled.set(bean, pv.getValue());
    }
}

Context component

As mentioned earlier, the function of Context component is to provide Spring with a runtime environment to save the state of various objects. Let's take a look at the class structure diagram related to Context.

As can be seen from the figure, the top-level parent class of the Context class structure is ApplicationContext. In addition to identifying the basic information of an application environment, it also inherits five interfaces, which mainly extend the function of Context. The subclasses of ApplicationContext mainly include two directions, which have been described in the figure. Next is the file type for constructing Context, and then How to access Context.

Generally, in traditional programming, whether using a factory to create an instance or directly creating an instance, the instance caller must actively create an instance before using it. Inverse of control refers to the so-called control inversion, which refers to the process of creating an instance by the container, and the caller hands over the control.

Dependence Injection further goes on the basis of control inversion. If there is no dependency injection, the container creates instances and saves them. The caller needs to use getBean(String beanName) to get the instances. When dependency injection is used, the container automatically injuses Bean instances to the callers who complete the corresponding configuration for their further use.

With the help of the above control inversion and dependency injection, the Context component helps to implement the Ioc container of Spring. Next, we take a Service class as the required Bean instance. In practical application, we will need Spring to manage many Bean instances.

public class SampleService {
    private String service;
        public String getService() {
        return service;
    }
    public void setService(String service) {
        this.service= service;
    }
}

During the running of the program, a SampleService is required. Instead of allowing the caller to create a new instance, we indicate in the configuration file that the instance of the SampleService is managed by the Spring container, and specify its initialization parameters. The configuration file is a resource, and its contents are as follows.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>
</beans>

The Spring Core component provides a ResourceLoader interface to facilitate reading xml files or other resource files. Its core function code should provide the following methods.

public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}

// Function code of UrlResource
public class UrlResource implements Resource {
    private final URL url;

    public UrlResource(URL url){
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

}

That is, load the resource file and return it in the form of data flow. Context generates the corresponding bean according to the definition in the resource and saves it in the container. The bean name is sampleService for further use by the program. This completes the inversion of control. Next, you need to inject sampleService where it needs to be used, that is, complete the dependency injection operation.

Now, assuming that the SampleService object is used in the SampleController, Spring provides three methods of dependency injection: constructor injection, setter injection and annotation injection.

public class SampleController {
    /**
     * 3\. Annotation injection
    **/
    /* @Autowired */
    private SampleService sampleService;

    /**
     * 1\. Constructor Injection 
    **/
    public SampleController(SampleService sampleService){
        this.sampleService = sampleService;
    }
    //non-parameter constructor 
    public SampleController(){}

    // Core functions of class
    public void process(){
        System.out.println(sampleService.getService());
    }
    /**
     * 2\. setter injection
    **/
    /*public void setService(SampleService service) {
        this.service= service;
    }*/
}

The three injection methods correspond to different configuration methods in the configuration file. Based on the previous xml file, we can implement these three injection methods respectively. It should be noted that the SampleController is also generated and managed using Spring's Ioc container.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>

<!-- 1\. The constructor injection method is SampleContorller of bean injection SampleService -->
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <!-- index Is the order of the corresponding parameters in the construction method -->
        <constructor-arg index="0" ref="sampleService"></constructor-arg>
    </bean>

<!-- 2\. setter The injection method is SampleContorller of bean injection SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <property name="sampleService " ref="sampleService"></property>
    </bean>
-->

<!-- 3\. The annotation injection method is SampleContorller of bean injection SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">

    <!-- No configuration is required, Spring Automatically inject corresponding information according to the type bean -->
    </bean>
-->
</beans>

Core components

An important part of Core components is to define the access mode of resources. The Core component abstracts all resources into an interface, so that the resource user does not need to consider the file type. For resource providers, there is no need to consider how to package resources for others to use (all resources in the Core component can be obtained through the InputStream class). In addition, the loading of resources in the Core component is completed by the ResourceLoader interface. As long as this interface is implemented, all resources can be loaded.

So, how do Context and Resource establish a relationship? We know from the previous introduction of Context that the classes or interfaces in the Context component finally implement the ResourcePatternResolver interface, which is used to load, parse and describe resources. This interface is equivalent to a connector in the Resource. It integrates the loading, parsing and definition of resources in the Resource to facilitate the use of other components.

Posted by kippy on Sat, 23 Oct 2021 03:32:08 -0700