preface

Why do we need Spring? What is Spring?

For such questions, most people are in a vague state, but not completely said, today we will try to solve the mystery of Spring from the perspective of architectural design.

This article is introduced in a very simple way, so don’t panic, I can assure you that you can understand it if you can program.

This article is based on Spring 5.2.8 and should take about 20 minutes to read

case

Let’s take a look at a case: there is a guy who has a Geely car and usually drives a Geely car to work

Code implementation:

public class GeelyCar {

    public void run(a){
        System.out.println("geely running"); }}Copy the code
public class Boy {
		/ / rely on GeelyCar
    private final GeelyCar geelyCar = new GeelyCar();

    public void drive(a){ geelyCar.run(); }}Copy the code

One day, the boy made money and bought a red flag and wanted to drive a new car.

Simple, change the dependency to HongQiCar

Code implementation:

public class HongQiCar {

    public void run(a){
        System.out.println("hongqi running"); }}Copy the code
public class Boy {
    // Change the dependency to HongQiCar
    private final HongQiCar hongQiCar = new HongQiCar();

    public void drive(a){ hongQiCar.run(); }}Copy the code

When you get tired of driving your new car and want to switch back to your old one, there’s a problem: the code keeps changing

Clearly, this case violates our dependency inversion principle (DIP): programs should rely not on implementations but on abstractions

The optimized

Now let’s optimize the code as follows:

Boy relies on the Car interface, while the previous GeelyCar and HongQiCar are implemented as Car interfaces

Code implementation:

Define the Car interface

public interface Car {

    void run(a);
}
Copy the code

Change the previous GeelyCar and HongQiCar to Car implementation class

public class GeelyCar implements Car {

    @Override
    public void run(a){
        System.out.println("geely running"); }}Copy the code

The same HongQiCar

Person now depends on the Car interface

public class Boy {
		// Depends on the interface
    private final Car car;
		
    public Person(Car car){
        this.car = car;
    }

    public void drive(a){ car.run(); }}Copy the code

At this point, the guy wants to change what car to drive, just pass in what parameters, the code will not change.

limitations

The above case does seem to have no problems after transformation, but there are still some limitations. If a new scene is added at this time:

One day the guy was drunk and couldn’t drive, so he needed a driver. The driver doesn’t care which guy he’s driving or what kind of car he’s driving. The guy suddenly becomes an abstraction, and the code changes again. The code that depends on the guy might look something like this:

private final Boy boy = new YoungBoy(new HongQiCar());
Copy the code

As the complexity of the system increases, such problems become more and more difficult to maintain, so how do we solve them?

thinking

First, we can be sure that there is nothing wrong with using the dependency inversion principle, which solves our problem to some extent.

We think the problem is in passing in parameters: we pass in whatever the program needs, which can become extremely complicated when the system has multiple dependent class relationships.

Maybe we can reverse our thinking: the program uses what we have!

When only HongQiCar and YoungBoy are implemented, YoungBoy with HongQiCar is used as the proxy driver!

When we only implemented GeelyCar and OldBoy, the surrogate driver automatically changed to OldBoy with GeelyCar!

How to reverse this is the big problem Spring solves.

Spring is introduced

Spring is a one-stop lightweight and heavyweight development framework designed to address the complexity of enterprise application development. It provides comprehensive infrastructure support for developing Java applications, allowing Java developers to focus on developing applications (CRUD) without having to worry about class to class dependencies.

Spring provides rich functionality for enterprise development, and the underlying functionality is dependent on its two core features: dependency injection (DI) and section-oriented programming (AOP).

The core concepts of Spring

The IoC container

IoC is also known as dependency injection (DI), which is a process of injecting objects through dependencies: Objects define their dependencies (that is, other objects combined with them) only through constructors, factory methods, or properties set on them when they are instantiated, and the container then injects these needed dependencies when creating beans. This process is essentially the inverse of the Bean itself (hence the name inversion of control) to control the instantiation or location of its dependencies by using directly built classes or mechanisms such as service location patterns.

The principle of dependency inversion is the design principle of IoC, and dependency injection is the realization of IoC.

The container

In Spring, configuration information can be written in XML, Java annotations, or Java code, and instructions for instantiating, configuring, and assembling application objects are called containers.

Typically, we just need to add a few annotations so that once the container is created and initialized, we have a configurable, executable system or application.

Bean

In Spring, the objects instantiated by the Spring IOC container — > assembly management — > make up the skeleton are called beans. Beans are just one of many objects in an application.

Together, Spring is an IoC container for beans, and dependencies between beans are handled through dependency injection.

AOP

Aspect-oriented Programming (AOP) is a functional supplement to OOP, which focuses on classes while AOP is Aspect. It plays a very important role in log processing, security management, transaction management and so on. AOP is an important component of the Spring framework, and while the IOC container does not rely on AOP, AOP provides very powerful capabilities to complement IOC.

AOP allows us to enhance our business functionality without modifying the existing code by cutting a piece of functionality into a location we specify, such as printing logs between method invocation chains.

The advantages of the Spring

1. Spring simplifies enterprise Java development through DI and AOP

2. Spring’s low-intrusion design makes code pollution extremely low

Spring’s IoC container reduces complexity between business objects and decouples components from each other

Spring’s AOP support allows for centralized processing of common tasks such as security, transactions, logging, and so on, leading to better reusability

5. The open nature of Spring does not force applications to rely entirely on Spring, and developers are free to choose part or all of the Spring framework

6. Spring’s high extensibility makes it easy for developers to integrate their frameworks on Spring

7. The Spring ecosystem is extremely complete, integrating various excellent frameworks so that developers can easily use them

We can do without Java, but we can’t do without Spring

Transform the case with Spring

Now that we know what Spring is, can we use Spring to tweak our case

The original structure remains unchanged, just add the @Component annotation to GeelyCar or HongQiCar, and the @AutoWired annotation to Boy when used

Code style:

@Component
public class GeelyCar implements Car {

	@Override
	public void run(a) {
		System.out.println("geely car running"); }}Copy the code

The same HongQiCar

In Spring, when a class identifies the @Component annotation, it indicates that it is a Bean that can be managed by the IoC container

@Component
public class Boy {
	
	// Use the Autowired annotation to indicate that car requires dependency injection
	@Autowired
	private Car car;

	public void driver(a){ car.run(); }}Copy the code

What we said earlier: the application uses whatever we implement, which is the same thing as identifying the Component annotation on any class that will be a Bean and Spring will use it to inject the Boy property Car

So when we annotate GeelyCar as Component, Boy’s car is GeelyCar, and when we annotate HongQiCar as Component, Boy’s car is HongQiCar

Of course, we can’t annotate Component annotations on both GeelyCar and HongQiCar, because Spring doesn’t know which Car to use for injection — Spring also has a choice problem.

Use Spring to start the program

// Tell Spring which package to scan beans from, @componentScan (basePackages = "com.my.spring.test.demo")public class Main {public static void Main (String[]) Args) {// Pass Main(configuration information) into ApplicationContext(IoC container) ApplicationContext Context = new AnnotationConfigApplicationContext(Main.class); // Get our boy boy = (boy) context.getbean ("boy"); / / boy driving. The driver (); }}
Copy the code

This is where we can explain what we’ve just learned about Spring

To have ComponentScan annotations (configuration information) the Main class, to AnnotationConfigApplicationContext initialized (IoC container), is equal to: the IoC container by getting configuration information instantiated, management, and assemble the Bean.

How is dependency injection done inside the IoC container, which is the focus of this article

thinking

We have a complete understanding of the basic functions of Spring and a concrete experience of the previous concepts through a transformation case. However, we do not know how the internal action of Spring’s dependency injection is completed, so it is more important to know why. Combining our existing knowledge and understanding of Spring, Take a wild guess (this is an important skill)

Guess is: if we were to do it ourselves, how would we do it?

First, we need to know what we need to do: scan the classes under the specified package, instantiate them, and assemble them according to their dependencies.

Step breakdown:

Scan the class under the specified package -> if the class identifies the Component annotation (which is a Bean) -> store the class information

Instantiate -> iterate over the stored class information -> instantiate those classes through reflection

According to the dependency combination -> parse the class information -> determine whether there are fields in the class that need dependency injection -> inject the fields

Plan implementation

Now that we have a solution that looks like that, let’s try to implement it

Custom annotation

Note: first of all, we need to define the needed ComponentScan, Component, Autowired

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {

    String basePackages(a) default "";
}
Copy the code
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {

    String value(a) default "";
}
Copy the code
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Autowired {}
Copy the code

Scans the classes under the specified package

Scanning all classes in a given package may sound confusing, but in fact it is equivalent to another problem: how to traverse a file directory?

So what should I use to store class information? Let’s look at the getBean method in the example above. Is it like the Map method that gets the value by key? There are many other implementations of maps, but only one thread-safe is ConcurrentHashMap(don’t talk to me about HashTable).

Define the map that holds the class information

private finalMap<String, Class<? >> classMap =new ConcurrentHashMap<>(16);
Copy the code

For the specific process, the code implementation is also attached below:

Code implementation, which can be viewed in conjunction with a flowchart:

Scanning information

private void scan(Class
        configClass) {
  // Parse the configuration class to obtain the scan package path
  String basePackages = this.getBasePackages(configClass);
  // Use the scan package path to traverse the file
  this.doScan(basePackages);
}
Copy the code
private String getBasePackages(Class
        configClass) {
  // Get the scan package path from the ComponentScan annotation
  ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
  return componentScan.basePackages();
}
Copy the code
private void doScan(String basePackages) {
  // Get resource information
  URI resource = this.getResource(basePackages);

  File dir = new File(resource.getPath());
  for (File file : dir.listFiles()) {
    if (file.isDirectory()) {
      // Recursive scanning
      doScan(basePackages + "." + file.getName());
    }
    else {
      // com.my.spring.example + . + Boy.class -> com.my.spring.example.Boy
      String className = basePackages + "." + file.getName().replace(".class"."");
      // Store the class in classMap
      this.registerClass(className); }}}Copy the code
private void registerClass(String className){
  try {
    // Load the class informationClass<? > clazz = classLoader.loadClass(className);// Determine whether to identify the Component annotation
    if(clazz.isAnnotationPresent(Component.class)){
      / / generated beanName com. My. Spring. Example. The Boy Boy - >
      String beanName = this.generateBeanName(clazz);
      // car: com.my.spring.example.CarclassMap.put(beanName, clazz); }}catch (ClassNotFoundException ignore) {}
}
Copy the code

instantiation

Now that you’ve resolved all the appropriate classes, it’s time to instantiate them

Define the Map that holds the Bean

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

Specific process, the following code is also given:

Code implementation, which can be viewed in conjunction with a flowchart:

Iterate through the classMap to instantiate the Bean

public void instantiateBean(a) {
  for(String beanName : classMap.keySet()) { getBean(beanName); }}Copy the code
public Object getBean(String beanName){
  // Get it from the cache
  Object bean = beanMap.get(beanName);
  if(bean ! =null) {return bean;
  }
  return this.createBean(beanName);
}
Copy the code
private Object createBean(String beanName){ Class<? > clazz = classMap.get(beanName);try {
    / / create a bean
    Object bean = this.doCreateBean(clazz);
    // Store the bean in the container
    beanMap.put(beanName, bean);
    return bean;
  } catch (IllegalAccessException e) {
    throw newRuntimeException(e); }}Copy the code
private Object doCreateBean(Class
        clazz) throws IllegalAccessException {
  // Instantiate the bean
  Object bean = this.newInstance(clazz);
  // Fill the field and set the value of the field
  this.populateBean(bean, clazz);
  return bean;
}
Copy the code
private Object newInstance(Class
        clazz){  try {    / / here only support the default constructor return clazz. GetDeclaredConstructor (). The newInstance (); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); }}
Copy the code
private void populateBean(Object bean, Class
        clazz) throws IllegalAccessException {  Final Field[] fields = clazz.getDeclaredFields(); clazz.getDeclaredFields(); for (Field field : fields) { Autowired autowired = field.getAnnotation(Autowired.class); if(autowired ! // Get the bean Object value = this.resolveBean(field.getType()); field.setAccessible(true); field.set(bean, value); }}}
Copy the code
private Object resolveBean(Class
        clazz){
  // Check whether clazz is an interface, and if so, check whether there are subclasses in classMap
  if(clazz.isInterface()){
    // Only one classMap subclass is supported for now
    for(Map.Entry<String, Class<? >> entry : classMap.entrySet()) {if (clazz.isAssignableFrom(entry.getValue())) {
        returngetBean(entry.getValue()); }}throw new RuntimeException("Unable to find bean for dependency injection");
  }else {
    returngetBean(clazz); }}Copy the code
public Object getBean(Class
        clazz){
  // The name of the generated bean
  String beanName = this.generateBeanName(clazz);
  // This corresponds to the original getBean method
  return this.getBean(beanName);
}
Copy the code

combination

The two core methods are now written. To combine them, I implement them in a custom ApplicationContext class using the following constructor:

public ApplicationContext(Class
        configClass) {
  // 1. Scan for classes in the specified package
  this.scan(configClass);
  // 2. Instantiate the scanned classes
  this.instantiateBean();
}
Copy the code

UML class diagram:

test

The code structure is the same as the example, and here we show if our own Spring works

It works well, the Chinese don’t cheat the Chinese

The source code is given at the end of this article

review

Now, we have carried out the implementation according to the envisaged scheme, and the operation has reached the expected effect. But a closer look, combined with our usual Spring scenarios, reveals a number of problems with this code:

Constructor injection is not supported, and method injection is not supported either, which is a functional loss.

2, the problem of loading Class information, we use classloader. loadClass method, although this avoids Class initialization (do not use class.forname method), but it is still inevitable to load the meta information of the Class into the meta space, when we scan the package for unnecessary classes, This is a waste of our memory.

3. Circular dependencies between beans cannot be resolved. For example, an object A depends on an object B, which in turn depends on an object A. If we look at the code logic, we will find that it will fall into an infinite loop.

4. Poor scalability. We write all the functions in one class. When we want to improve the functions (such as the above three problems), we need to modify this class frequently, which will become more and more bloated.

Optimization scheme

For the first three problems are similar to the function of the problem, function, change to change.

The fourth issue we need to focus on is that in order for a framework to be good, it must be able to iterate well so that it can be rich in features. There are many factors that affect the ability to iterate, one of which is extensibility.

So how to improve the scalability of our scheme, the six design principles give us a good guide.

In the scenario, ApplicationContext does a lot of things, which can be broken down into two main chunks

1. Scan the classes in the specified package

2. Instantiate the Bean

Use the idea of the single responsibility principle: a class does one thing, a method does one thing.

We scan under the specified package. These make it separate the use of a processor for processing, because the scan configuration from the configuration class, then we can call him a configuration class processor: ConfigurationCalssProcessor

The same goes for instantiating beans, which is divided into two things: instantiation and dependency injection

Instantiating a Bean is the equivalent of producing a Bean. We will do this using a factory Class called BeanFactory. Since we are producing beans, we need raw materials (Class), so we define both classMap and beanMap here

And dependency injection process, is actually in the treatment of the Autowired annotations, it is called: AutowiredAnnotationBeanProcessor

We’re also learning that in Spring, there’s not just this way of using it, there’s XML, MVC, SpringBoot, so we’re abstracting the ApplicationContext and just implementing the trunk flow, The original method to a AnnotationApplicationContext annotations.

Use dependency inversion: programs should rely on abstractions

In the future, the kind of information can not only from the type of information, also can from a configuration file, so we’ll ConfigurationCalssProcessor abstractions

And dependency injection approach doesn’t have to be identified by Autowried annotation, also can be other annotations identity, such as Resource, so we’ll AutowiredAnnotationBeanProcessor abstractions

A Bean can have many types, either singleton, multi-instance, or factory Bean, so we abstract the BeanFactory

Now, we’ve optimized our solution using two design principles, and it’s completely different from what it was before.

The design of the Spring

In the previous step, we implemented our solution and scaled it based on some assumptions. Now let’s take a look at what Spring actually looks like

So what are the “roles” in Spring?

1, beans: Spring as an IoC container, of course, the most important is beans

BeanFactory: a factory that produces and manages beans

3. BeanDefinition: The Bean definition, the Class in our schema, which Spring encapsulates

BeanDefinitionRegistry: Similar to the relationship between beans and BeanFactory, the BeanDefinitionRegistry is used to manage BeanDefinitions

5, BeanDefinitionRegistryPostProcessor: used to parse the configuration class of the processor, similar to ClassProcessor in our plan

6, spring BeanFactoryPostProcessor: BeanDefinitionRegistryPostProcessor parent class, let we can parse configuration for post processing after class

7, BeanPostProcessor: Bean post processor, for some processing in the process of producing Bean, such as dependency injection, AutowiredAnnotationBeanProcessor similar to ours

8, ApplicationContext: The ApplicationContext is the face of Spring. The ApplicationContext is a combination of BeanFactory, so it completely extends the functionality of BeanFactory. And added more enterprise-specific features on top of it, such as the familiar ApplicationListener

This is a bit of an inversion of the cart, because the implementation in our scenario is actually similar to the implementation in Spring, just to make it easier to understand

After going through the design and optimization of our own scheme, these characters are actually very easy to understand

Next, let’s take a look at each one in detail

BeanFactory

BeanFactory is a top-level interface in Spring that defines how to get beans, and there’s another interface in Spring called SingletonBeanRegistry that defines how to manipulate singleton beans. I’ll introduce the two together here because they’re basically the same. SingletonBeanRegistry also notes that it can be implemented together with the BeanFactory interface to facilitate unified management.

BeanFactory

ListableBeanFactory: interface that defines methods associated with getting a list of beans/BeanDefinitions, such as getBeansOfType(Class Type)

2, AutowireCapableBeanFactory: interface, defines the Bean lifecycle related methods, such as creating a Bean, dependency injection, initialization

AbstractBeanFactory: Abstract class that basically implements all methods related to Bean operations and defines abstract methods related to the Bean lifecycle

4, AbstractAutowireCapableBeanFactory: an abstract class, inherited AbstractBeanFactory, implements the Bean lifecycle related content, although is an abstract class, but it has no abstract methods

5, DefaultListableBeanFactory: inheritance and implementation of all classes and interfaces, is the bottom of the BeanFactory in for Spring, its ListableBeanFactory interface is realized

ApplicationContext: Is also an interface, which is covered below

SingletonBeanRegistry

1, DefaultSingletonBeanRegistry: defines the Bean buffer pool, similar to our BeanMap, achieved concerning the operation of the singleton, such as getSingleton (interview often ask l3 cache is here)

2, FactoryBeanRegistrySupport: provides support for FactoryBean, such as access to Bean from the FactoryBean

BeanDefinition

BeanDefinition is also an interface (surprise) that defines a number of class-related operations that can be used directly when producing beans, such as getBeanClassName

Its general structure is as follows (here is an example of a RootBeanDefinition subclass) :

Inside the various attributes must be no stranger to you

Again, it has a number of implementation classes:

1, AnnotatedGenericBeanDefinition: configuration class to parse the Import annotations into classes, and will use it for encapsulation

2, ScannedGenericBeanDefinition: encapsulated by @ ComponentScan scanning packets of information

3, ConfigurationClassBeanDefinition: encapsulated by @ Bean annotations of class information

4, RootBeanDefinition: ConfigurationClassBeanDefinition parent class, usually in the Spring for internal use, will other BeanDefition into that class

BeanDefinitionRegistry

Defines the BeanDefiniton related operations, such as registerBeanDefinition getBeanDefinition, in the BeanFactory, implementation class is DefaultListableBeanFactory

BeanDefinitionRegistryPostProcessor

The Spring team used to say that the names of the classes in Spring are very literal, so it is a very comfortable thing to look at the source code. You can guess their functions by looking at the class names and method names.

This interface defines only one function: to process BeanDefinitonRegistry, which is to resolve Import, Component, ComponentScan and other annotations in the configuration class for corresponding processing, and then register these classes as the corresponding BeanDefinition

In the Spring, only an implementation: ConfigurationClassPostProcessor

BeanFactoryPostProcessor

A post-processor called the BeanFactory, which defines the processing logic that can be called after the configuration class has been parsed, and is like a slot that we can implement if we want to do something after the configuration class has been parsed.

In the Spring, the same only ConfigurationClassPostProcessor realized it: added the Configuration used to deal with the special class of annotation

Here is a small problem, such as the following code:

@Configuraiton
public class MyConfiguration{
  @Bean
  public Car car(a){
      return new Car(wheel());
  }
  @Bean
  public Wheel wheel(a){
      return newWheel(); }}Copy the code

Q: How many times does the Wheel object get new when Spring starts? Why is that?

BeanPostProcessor

A backend processor for beans

This post-processor is invoked nine times throughout the Bean’s life cycle during Bean creation, which we will explore next time. Its implementation classes and functions are described below

1, AutowiredAnnotationBeanPostProcessor: used to infer the constructor instantiated, and dealing with Autowired and Value annotation

2, CommonAnnotationBeanPostProcessor: processing the annotations in Java specification, such as Resource, PostConstruct

3, ApplicationListenerDetector: use after Bean initialization, will implement the interface ApplicationListener Bean to add to the list of event listeners

4, ApplicationContextAwareProcessor: for callback Aware the Bean of the interface was realized

5, ImportAwareBeanPostProcessor: used for Bean callback realized ImportAware interface

ApplicationContext

The ApplicationContext, as the core of Spring, isolates the BeanFactory in facade mode, defines the skeleton of the Spring startup process in template method mode, and calls various Processors…… in policy mode How intricate and wonderful it is!

Its implementation class is as follows:

1, ConfigurableApplicationContext: interface, defines the configuration and life cycle related operations, such as the refresh

2, AbstractApplicationContext: Abstract class that implements the Refresh method. The Refresh method is the core of the Spring core, so you can say that the whole Spring is in REFRESH. All subclasses are started by the refresh method, and after calling this method, all singleton instances are instantiated

3, AnnotationConfigApplicationContext: at boot time related to the use of annotations reader and scanner, the water in the Spring container to register the processor, and then the main process in the refresh method call

4, AnnotationConfigWebApplicationContext: loadBeanDefinitions method to be invoked in the refresh process, loading BeanDefintion thereby

5, ClassPathXmlApplicationContext: same as above

Can be seen from the subclass, subclasses of the difference is how to load BeanDefiniton, AnnotationConfigApplicationContext is through configuration class processor (ConfigurationClassPostProcessor) loaded, AnnotationConfigWebApplicationContext and ClassPathXmlApplicationContext loadBeanDefinitions method to realize by oneself, other processes are identical

Spring’s process

Now that we’ve seen the main roles and roles in Spring, let’s try to put them together to build a Spring startup process

Also in our common AnnotationConfigApplicationContext, for example

This diagram only gives a partial overview of the flow in Spring, which will be covered in more detail in a later chapter

summary

It is said that all things are difficult at the beginning. The original intention of this paper is to enable people to understand Spring from the simple way to the deep way, initially establish the cognitive system of Spring, understand the internal architecture of Spring, and no longer superficial cognition of Spring.

Now the head has started, I believe that the content of the study will also water to the canal.

This article is not only about the architecture of Spring, but hopefully will be a guide for us to review the overall content of Spring in the future.

Finally, after reading this article, I believe it is easy to answer the following common interview questions

1. What is a BeanDefinition?

BeanFactory and ApplicationContext

3. Classification and function of post-processor?

4. What is the main flow of Spring?

Read the article again if you don’t have a good answer, or leave a comment in the comments section

I’m Aobing, the more you know, the more you don’t know, thank you for your talent: likes, favorites and comments, we’ll see you next time!