Author: DeppWang, original address

I really got started with the Spring framework by implementing a simple Spring IoC container. This article is a summary of the implementation process, with the need to read the source code, source address.

Combining this article with the source code, you should be able to learn: The principle of Spring and Spring Boot principle.

The Spring framework is developed in Java, which is an object-oriented language, so the Spring framework itself has a lot of abstractions, inheritance, and polymorphism. For beginners, just sorting out their logic was a hassle, so I ditched the packaging and just implemented the essential functionality. The code isn’t very rigorous, but it’s enough just to understand Spring ideas.

The text begins.

Zero, the role of Spring

In the ancient days before the Spring framework, our business logic looked like this:

public class PetStoreService {
    AccountDao accountDao = new AccountDao();
}

public class AccountDao {
}

PetStoreService petStoreService = new PetStoreService();
Copy the code

The new keyword is everywhere and requires developers to explicitly use the new keyword to create objects (instances). This has many drawbacks, such as creating too many objects (creating multiple objects multiple times), too much coupling (default new), and so on.

Rod Johnson got so upset about this that he created a framework called Spring to eliminate the new keyword (haha, I made it up, just to show what Spring does).

With the Spring framework, which creates objects, manages them, and handles dependencies between them, we programmers can focus on business logic (moving bricks) and not on object creation. Let’s look at how the Spring framework is implemented.

Note: The following abbreviation of the Spring framework is Spring

This section corresponds to the source code: V0

Implementing “instantiated beans”

First, we need to mark which classes to hand over to Spring to manage. We can use the XML tag to mark them as

:

<! --petstore-v1.xml-->
    <bean id="petStore" class="org.deppwang.litespring.v1.service.PetStoreService"></bean>
    <bean id="accountDao" class="org.deppwang.litespring.v1.dao.AccountDao"></bean>
Copy the code

The first step in Spring is to instantiate a class based on XML tags. In Spring, we call classes beans, so instantiated classes can also be called instantiated beans.

How does Spring implement instantiated classes based on XML configuration?

It can be roughly divided into three steps (read with source v1) :

  1. Get the Bean information from the XML configuration file: ID, fully qualified name, as an attribute of the BeanDefinition (Bean definition class)

    BeanDefinition bd = new BeanDefinition(id, beanClassName);
    Copy the code
  2. Use a Map for all beanDefinitions, and Spring is essentially a Map for all BeanDefinitions

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
    Copy the code
  3. When a Bean instance is obtained, its class object is obtained through the class loader based on the fully qualified name, and the Bean instance is created through the class object using reflection

    return Thread.currentThread().getContextClassLoader().loadClass(bd.getBeanClassName()).newInstance();
    Copy the code

For class loading and reflection, see Chapter 7 of Understanding the Java Virtual Machine in Depth, and for reflection and reflection in Spring. This article only studies Spring, and these two knowledge points will not be discussed in depth.

Noun explanation:

  • Fully qualified name: the path of the compiled class file in the JAR package

This section corresponds to the source code: V1

2. Implement “Populate Properties (dependency Injection)”

public class PetStoreService {
    AccountDao accountDao;
}
Copy the code

When the Bean is instantiated, the member variable (attribute) will still be null:

You need a way to do that so that the property is not null, and we call this the fill property step.

When a Bean’s member variable type is another Bean, we can say that one Bean is dependent on another Bean. So the population property can also be called Dependency Injection (DI).

Spring aside, we normally have two ways to implement dependency injection: 1. Use constructors; 2. Use Setter() methods. Constructors are generally used (because there may be multiple dependencies) :

public class PetStoreService {
    private AccountDao accountDao;
    public PetStoreService(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

public class AccountDao {
}

PetStoreService petStore = new PetStoreService(new AccountDao()); // Inject the dependency new AccountDao() into petStore
Copy the code

Spring also implements dependency injection in these two ways.

We need to tell Spring how and what dependencies we need to inject for the class. For example, we want Spring to inject AccountDao dependencies using constructors.

    <bean id="petStore" class="org.deppwang.litespring.v2.service.PetStoreService">
        <constructor-arg ref="accountDao"/> 
    </bean>
Copy the code

How does Spring implement dependency injection based on

?

It can be roughly divided into 3 steps (read with source v2) :

  1. Get the id of the constructor’s argument instance from the XML and store it in the BeanDefinition’s constructorArguments

    bd.getConstructorArgumentValues().add(argumentName);
    Copy the code
  2. Get all the Constructor objects of PetStoreService by reflection, and find the Constructor objects whose arguments match those of constructorArguments

    Constructor<? >[] candidates = beanClass.getDeclaredConstructors();Copy the code
  3. Get all the parameter instances with constructorArguments, and then use reflection to fill the properties with a Constructor object.

    return constructorToUse.newInstance(argsToUse);
    Copy the code

The way to implement dependency injection based on Setter() methods is similar to constructors, and the implementation is available in the source code.

In fact, Spring tries to inject dependencies using constructors first by default, using when

is not configured.

Because Spring implements dependency injection, we programmers don’t have Control over creating objects, which is also known as Inversion of Control. Because Spring uses A Map to manage BeanDefinitions, we can also call Spring an IoC container.

This section corresponds to the source code: V2

3. Use “singleton pattern, factory Method pattern”

The previous two steps enable Bean instances to be created when a Bean instance is acquired, but Bean instances are often used and cannot be newly created each time. In Spring, a Bean corresponds to only one Bean instance, which requires the singleton pattern.

Singleton pattern: A class has one and only one instance

How does Spring implement the singleton pattern by using class objects to create Bean instances?

Spring actually uses a Map to hold all Bean instances. If there is no Bean instance in the Map, create the Bean instance. To obtain, obtain it directly from the Map. This ensures that a class has only one Bean instance.

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64);
Copy the code

Because hosting Bean instances is also a Map, this is another reason Spring is called an IoC container, in addition to beandefinitionMaps.

We give control of creating objects to Spring (BeaniocContainer.java), which we can think of as a factory for creating objects (specializing in producing objects), or as a simple factory. It implements object creation and object usage separation.

In order for Spring to implement instantiated beans in different ways, it needs to use the factory method pattern rather than just a simple factory.

Factory method pattern: Defines an interface for creating objects and lets subclasses decide which class to instantiate. The Factory Method delays the instantiation of a class to its subclasses. Source: Head First Design Patterns

The simple idea is to abstract the method that creates the object as a factory method.

Let a subclass decide which class to instantiate. Let a subclass decide how to implement an instantiated class.

We can think of the factory method pattern as an improved version of a simple factory, which can create objects in a variety of ways by subclassing them, and is a “polymorphism” of a simple factory.

Early Spring’s strategy for using beans was to instantiate beans on demand, notably the XmlBeanFactory. In order to achieve more functionality, we added the ApplicationContext, which instantiates all beans at initialization, and both inherit from the BeanFactory interface.

Implementation: Change BeanIocContainer to the BeanFactory interface, providing only the getBean() method. Instantiate beans in a way that implements different subclasses corresponding to different things.

Spring uses **getBean() ** as the factory method. GetBean () contains methods to create the object.

This section corresponds to v3

4. Realize “Annotations”

In business development, it would be cumbersome and error-prone to set up constructors for each business class that needed to be configured in XML. Spring, starting with 2.5REF, has added annotations that can be used to replace XML configuration and constructors for business classes.

  • Use the @Component annotation instead<bean>
  • Use the @autowired annotation instead<constructor-arg>+ constructor.
@Component
public class PetStoreService {
    @Autowired
    private AccountDao accountDao;
}
Copy the code

How does Spring implement instantiated beans and dependency injection based on annotations? Or, what do these two notes do?

@Component is used to generate BeanDefinition.

  • Find all Class files that contain @Component annotations in the path specified by

    as BeanDefinitions

    String basePackagesStr = ele.attributeValue("base-package");
    Copy the code
  • How to tell if a Class has @Component: Using bytecode technology, get the metadata in the Class file (annotations) and tell if @Componet is in the metadata

    annotationMetadata.hasAnnotation(Component.class.getName())
    Copy the code

2, @autowired for dependency injection, implementation principle (with source v4 read) :

  • Use reflection to get all the attributes (Field objects) and see if there are @AutoWired annotations in the Field object. If there are, use reflection to implement dependency injection

    Field[] fields = bean.getClass().getDeclaredFields();
    field.set(bean, getBean(field.getName()));
    Copy the code
  • Note: With @autowired, neither constructors nor Setter() methods are used

@Component + @autoWired implements Spring management of business classes. A business class decorated with @Component + @autowired is a special kind of Bean.

At this point, we still need to implement component scanning through configuration files. Is there a way not to use configuration files at all? There are!

We can use @Configuration instead of the Configuration file and @ComponentScan instead of the < Context: Component-scan > of the Configuration file.

@Configuration // Mark the class @configuration to indicate that the class is equivalent to a Configuration file
@ComponentScan // ComponentScan Scans the path of petStoreConfig. class and all its subpaths
public class PetStoreConfig {
    public static void main(String[] args) {
        ApplicationContext context = newAnnotationConfigApplicationContext(PetStoreConfig.class); PetStoreService userService = context.getBean(PetStoreService.class); userService.getAccountDao(); }}Copy the code

Using annotations is the same as using XML configuration files. The purpose is to use the configuration class as an entry point for scanning components and loading them into the IoC container.

AnnotationConfigApplicationContext class is designed for for configuration class started. The implementation mechanism is available on Google.

In Spring, we call classes beans. That’s not entirely true. For a class to be called a Bean, one condition needs to be met:

  • When you have a member variable, you either have an @Autowired annotation or a corresponding constructor or Setter() method

That is, classes that can be managed by Spring, called beans.

Noun explanation:

  • Component: a Component
  • Autowired: Automatic assembly

This section corresponds to the source code: V4

5. Annotation @bean

As long as the class is a Bean, it can be managed by Spring.

Business classes can use @Component + @AutoWired to implement dependency injection to reduce configuration.

Injecting other beans into the container, which can be done through XML, is still a bit of a hassle. Spring provides an annotation @bean, and when a method is marked @bean, its return value is injected into the container.

For example, we can inject a thread pool Executor instance into a container and use @autowired:

@Configuration
public class BeanConfig {
    @Bean
    public ExecutorService sendMessageExecutor(a) {
        ExecutorService executor = new ThreadPoolExecutor(2.2.0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), new ThreadPoolExecutor.AbortPolicy());
        returnexecutor; }}public class BeanTest {
    @Autowired
    ExecutorService service;
}
Copy the code

This is cumbersome and not intuitive to do in XML.

Note: @bean needs to be used with @Configuration

This section corresponds to the source: V5

Six, Spring Boot principle

@Configuration and @ComponentScan are mentioned in the previous section, which means Spring Boot has to be mentioned. Because SpringBoot uses @Configuration and @ComponentScan, you can see that by clicking @SpringBootApplication.

We found that the Spring Boot startup, does not use AnnotationConfigApplicationContext to specify the start x Config class. This is because it uses the @enableAutoConfiguration annotation.

Spring Boot leverages @enableAutoConfiguration to automatically load Configuration classes identified as @Configuration into the container. Spring Boot can also put configuration classes that need to be loaded automatically in Spring. factories, and Spring Boot will automatically load the configuration classes in Spring.Factories. Spring. factories should be placed under meta-INF.

For example, when a Spring Boot project starts, the autoCoFigure package will automatically load (partial) configuration classes into the container as follows:

That’s how Spring Boot works.

In Spring Boot, we introduce jar packages that have a field called starter, which we call starter packages.

The reason it’s identified as a starter is because we don’t have to set up any extra operations when we introduce these packages, which can be assembled automatically. Starter packages typically include their own Spring.Factories. Such as spring – the cloud – starter – eureka – server:

As a druid – spring – the boot – starter:

Sometimes we also need to customize the starter package. For example, in Spring Cloud, when an application calls another application’s code, either the caller uses Feign (HTTP), or the caller can customize the called as a starter package and let the caller rely on references. Used by @autowired. You need to set up the configuration class and spring.factories on the called side:

@Configuration
@ComponentScan
public class ProviderAppConfiguration {
}

// spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.amy.cloud.amycloudact.ProviderAppConfiguration
Copy the code

Of course, you can put both files on the caller (specifying the scan path), but usually on the called. Ps: If your two applications have the same base-package path, you can skip this step.

Speaking of Spring Boot, in Spring MVC, how do you inject a jar package into a container?

  • As with scanning this project package, in XML, add the scan path for the imported JAR package:
<context:component-scan base-package="Introduce base-package jar package" />.Copy the code

Well, there is no source code for this section

Seven, conclusion

Spring is based on two key words: reflection and annotations.

  • Reflection: Reflection is required for both Bean instance creation and dependency injection.
  • Annotations: Using annotations can greatly improve code readability and reduce complexity. An annotation is essentially an identifier that requires bytecode technology to obtain.

We rarely use XML to set up beans these days, but knowing XML makes it easier to understand how Spring annotations work.

Here’s a note summary:

  • @Component is a Component identifier that requires Spring management
  • Instead of the constructor, @autowired is used to determine whether dependency injection is needed
  • @ComponentScan Specifies the component scanning path. If this parameter is not specified, it indicates the current path
  • @Configuration represents the Configuration class as Spring’s entry point for finding beans that need to be managed
  • The @Bean implementation injects any Bean into the container
  • @enableAutoConfiguration implements automatic loading of configuration classes to containers

The above implements a simple Spring IoC container, with a brief introduction to the Spring Boot principle. Spring also has many important features, such as handling dependencies between objects, managing the Bean lifecycle, implementing AOP, and so on. I’ll have a chance to share again later.

The full text.