41
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

👨‍💻 It is hard to even read the code written by a colleague, and still read Spring? 160fe7299a975e 160fe7299a9760, Spring is hard to read!

This Spring, who gets along with our farmers day and night, is like a daughter-in-law sleeping next to you. You know you want to find her for food, drink, pocket money, and skin. But you don't know how much grain she has in her warehouse, or whether she bought it for financial management or deposited it in the bank. Just kidding, I'm going to be serious next!


1. Why is Spring hard to read?

Why Spring is used every day, but it is so difficult to read the source code! Because Java and J2EE development experts Rod Johnson in 2002 and subsequently created the Spring framework, with the development of the JDK version and market needs, it has become bigger and bigger so far!

When you read its source code you will feel:

  1. ? It’s not as simple as writing code by yourself. 160fe7299a996d
  2. Why so many interfaces and interface inheritance, class B inherited by class A also implements interface X implemented by class A
  3. Simple factory, factory method, agent mode, observer mode, how to use it, there will be so many design patterns used
  4. It is resource loading, application context, IOC, AOP, and the life cycle of Bean. Where does the piece of code start?

how to , these are some of the problems you encountered while reading Spring, right? In fact, not only you can even say that as long as you are a code farmer in this industry, you will feel like you don't know where to start if you want to read the Spring source code. So I thought of a way, since Spring is too big to understand, then I try to start with a small Spring, hand achieve a Spring is not understandable and better, let alone the effect is really good, It took nearly 2 months to implement a simple version of Spring. Now, my understanding of Spring has been greatly improved, and I can read the source code of Spring.

Two, share hands-on Spring

By handwriting a simplified version of Spring framework , understand the core principles of Spring. In the process of handwriting, the Spring source code will be simplified, the core logic in the overall framework will be extracted, the code implementation process will be simplified, and the core functions will be retained, such as: IOC, AOP, Bean life cycle, context, scope, resource processing and other content implementation.

source code : https://github.com/fuzhengwei/small-spring

1. Implement a simple Bean container

Any implementation of a specific data structure that can store data can be called a container. For example: ArrayList, LinkedList, HashSet, etc., but in the context of the Spring Bean container, we need a data structure that can be used for storage and name indexing, so choosing HashMap is the most appropriate.

Here is a brief introduction to HashMap. HashMap is a zipper addressing data structure based on disturbance function, load factor, red-black tree conversion and other technical content. It can make data more hashed and distributed in hash buckets and collisions. Form the linked list and the red-black tree. Its data structure will maximize the complexity of reading the entire data between O(1) ~ O(Logn) ~ O(n). Of course, there will be O(n) linked list lookups in extreme cases. The situation with more data. However, we have passed the 100,000 data perturbation function and re-addressing verification test, and the data will be evenly hashed on each hash bucket index, so HashMap is very suitable for use in the container implementation of Spring Bean.

Another simple implementation of Spring Bean container requires three basic steps of Bean definition, registration, and acquisition. The simplified design is as follows:

  • Definition: BeanDefinition, maybe this is a class you often see when you look up Spring source code, for example, it will include singleton, prototype, BeanClassName, etc. But at present, our preliminary implementation will make it easier to deal with, and only define an Object type for storing objects.
  • Registration: This process is equivalent to storing the data in the HashMap, but now the HashMap stores the object information of the defined Bean.
  • Obtain: Finally, the object is obtained. The name of the Bean is the key. After the Spring container initializes the Bean, it can be obtained directly.

2. Use design patterns to realize the definition, registration, and acquisition of Beans

To complete the Spring Bean container, the first very important point is that only one class information is registered when the Bean is registered, and the instantiation information is not directly registered in the Spring container. Then you need to modify the attribute Object in the BeanDefinition to Class. The next thing you need to do is to process the instantiation operation of the Bean object and determine whether the current singleton object has been cached in the container when obtaining the Bean object. The overall design is shown in Figure 3-1

  • First of all, we need to define a Bean factory such as BeanFactory, and provide the method of obtaining Bean getBean(String name) , and then this Bean factory interface is implemented by the abstract class AbstractBeanFactory. In this way, the use of the template pattern design method can unify the calling logic and standard definitions of the common core method, and it is well controlled that subsequent implementers do not need to care about the calling logic and execute in a unified manner. Then the inheritors of the class only need to care about the logical implementation of the specific method.
  • Then the AbstractAutowireCapableBeanFactory that inherits the abstract class AbstractBeanFactory can implement the corresponding abstract methods, because AbstractAutowireCapableBeanFactory itself is also an abstract class, so it only implements its own abstract methods, and other abstract methods are implemented by classes that inherit AbstractAutowireCapableBeanFactory. Here is the performance of each role in the class implementation process, you only need to care about the content that belongs to you, not your content, don't participate.
  • In addition, there is a very important point of knowledge here, which is about the implementation of the interface definition of the singleton SingletonBeanRegistry, and after the DefaultSingletonBeanRegistry implements the interface, it will be inherited by the abstract class AbstractBeanFactory. Now AbstractBeanFactory is a very complete and powerful abstract class, and it can also very well reflect its abstract definition of the template pattern.

3. Implementation of class instantiation strategy with constructor based on Cglib

The technical design of filling this hole mainly considers two parts. One is how to transfer the input parameter information of the constructor to the instantiation operation in a reasonable way from the string process, and the other is how to instantiate the object containing the constructor.

图 4-1

  • Refer to the implementation of the Spring Bean container source code, and add the Object getBean(String name, Object... args) interface to the BeanFactory, so that the input parameter information of the constructor can be passed in when the Bean is obtained.
  • Another core content is what method is used to create a Bean object with a constructor? There are two ways to choose, one is based on Java's own method DeclaredConstructor , and the other is to use Cglib to dynamically create Bean objects. Cglib is implemented based on the bytecode framework ASM, so you can also directly create objects through the ASM operation instruction code

4. Inject properties into the Bean object and depend on the implementation of the Bean function

In view of the attribute filling is to complete the attribute information after the newInstance or Cglib , then you can add the completion attribute method in the createBean method of the AbstractAutowireCapableBeanFactory You can also learn from the Spring source code during the internship. The implementation here is also a simplified version of Spring. The follow-up comparison study will be easier to understand

  • Attribute filling must be created after the class is instantiated, that is applyPropertyValues operation AbstractAutowireCapableBeanFactory the createBean method of 060fe7299a9ffc.
  • Since we need to fill in property operations when creating a Bean, we need to add PropertyValues information in the bean definition BeanDefinition class.
  • In addition, the filling attribute information also includes the object type of the Bean, that is, you need to define a BeanReference, which is actually a simple Bean name, which is created and filled recursively during the specific instantiation operation, which is the same as the Spring source code implementation. Spring source code is an interface

5. Design and implement resource loader, parse and register Bean objects from Spring.xml

According to the requirements background of this chapter, we need to add a resource parser to the existing Spring framework prototype, which is able to read the configuration content of the classpath, local files and cloud files. These configuration contents are like Spring.xml configured when using Spring, which will include the description and attribute information of the Bean object. After reading the configuration file information, the next step is to perform the registration operation after parsing the Bean description information in the configuration file, and register the Bean object in the Spring container. The overall design structure is as follows:

  • The resource loader is a relatively independent part. It is located in the IO implementation content of the Spring framework core package and is mainly used to process file information in Class, local and cloud environments.
  • When the resource can be loaded, the next step is to parse and register the Bean into Spring. This part of the implementation needs to be combined with the DefaultListableBeanFactory core class, because all your parsed registration actions will put the Bean definition information into this class middle.
  • Then, when implementing, design the implementation level relationship of the interface, including we need to define the read interface BeanDefinitionReader defined by the Bean and the corresponding implementation class, and complete the analysis and registration of the Bean object in the implementation class.

6. Design and implement resource loader, parse and register Bean objects from Spring.xml

In order to be satisfied with performing user-defined operations from registration to instantiation of the Bean object, it is necessary to insert an interface class during the definition and initialization of the Bean, and this interface can then be externally used to implement the services that you need. Then combined with the processing capabilities of the Spring framework context, we can meet our target needs. The overall design structure is as follows:

  • The two interfaces that are satisfied with the extension of the Bean object are actually two very heavyweight interfaces in the Spring framework: BeanFactoryPostProcess and BeanPostProcessor , which are almost two necessary interfaces for everyone using the Spring framework to add additional development requirements for their own formation. .
  • BeanFactoryPostProcessor is a container extension mechanism provided by the Spring framework, which allows modification BeanDefinition
  • BeanPostProcessor is also an extension mechanism provided by Spring, but BeanPostProcessor modifies the Bean object after the Bean object is instantiated, and can also replace the Bean object. This part is closely related to the AOP to be implemented later.
  • At the same time, if you just add these two interfaces without any packaging, it is still very troublesome for users. We hope that in the development of Spring's context operation classes, the corresponding XML loading, registration, instantiation, and new modifications and extensions are integrated, so that Spring can automatically scan for our new services, which is convenient for users to use.

7. Implement application context, automatic identification, resource loading, and extension mechanism

It is possible that in the face of a huge framework like Spring, the exposed interface definitions or xml configuration, and the completion of a series of extensibility operations, make the Spring framework look very mysterious. In fact, for the additional processing operations added during the initialization of the Bean container, it is nothing more than pre-executing a defined interface method or reflecting the method configured in the xml in the class. In the end, you only need to implement it according to the interface definition, and there will be Spring The container only makes calls during processing. The overall design structure is as follows:

  • Add spring.xml configuration init-method、destroy-method two notes, in the course of the configuration file that is loaded in the notes together define configuration attributes to BeanDefinition of them. In this way, in the project of the initializeBean initialization operation, the method information configured in the Bean definition attribute can be called by reflection. In addition, if it is the way of interface implementation, you can directly call the method defined by the corresponding interface through the Bean object, ((InitializingBean) bean).afterPropertiesSet() . The effect of the two methods is the same.
  • In addition to the initialization operations, destroy-method and DisposableBean interfaces will be executed during the Bean object initialization phase, and the information of the registration and destruction method will be transferred to the disposableBeans attribute in the DefaultSingletonBeanRegistry class. This is for subsequent unified operations. There is also the use of a section of adapters, because reflection calls and direct interface calls are two ways. Therefore, you need to use an adapter for packaging. In the code explanation below, refer to the specific implementation of DisposableBeanAdapter
    -The destruction method needs to be operated before the virtual machine is closed, so here you need to use a registration hook operation, such as: Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!"))); This code you can execute the test , and you can manually call the ApplicationContext.close method to close the container .

8. Register the hook to the virtual machine to implement the initialization and destruction methods of the Bean object

It is possible that in the face of a huge framework like Spring, the exposed interface definitions or xml configuration, and the completion of a series of extensibility operations, make the Spring framework look very mysterious. In fact, for the additional processing operations added during the initialization of the Bean container, it is nothing more than pre-executing a defined interface method or reflecting the method configured in the xml in the class. In the end, you only need to implement it according to the interface definition, and there will be Spring The container only makes calls during processing. The overall design structure is as follows:

  • Add spring.xml configuration init-method、destroy-method two notes, in the course of the configuration file that is loaded in the notes together define configuration attributes to BeanDefinition of them. In this way, in the project of the initializeBean initialization operation, the method information configured in the Bean definition attribute can be called by reflection. In addition, if it is the way of interface implementation, you can directly call the method defined by the corresponding interface through the Bean object, ((InitializingBean) bean).afterPropertiesSet() . The effect of the two methods is the same.
  • In addition to the operations performed during initialization, destroy-method and DisposableBean interfaces will be executed in the Bean object initialization phase, and the information of the registration and destruction method will be transferred to the disposableBeans attribute in the DefaultSingletonBeanRegistry class. This is for subsequent unified operations. There is also the use of an adapter here, because reflection calls and direct interface calls are two ways. So you need to use the adapter for packaging, the following code explanation refers to the specific implementation of DisposableBeanAdapter
    -The destruction method needs to be operated before the virtual machine is closed, so here you need to use a registration hook operation, such as: Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!"))); This code you can execute the test , and you can manually call the ApplicationContext.close method to close the container .

9. Define the mark type Aware interface to realize the awareness of container objects

If I want to get some of the resources provided in the Spring framework, then I first need to consider how to obtain them, and then how you define the method of obtaining them in the Spring framework, and how to implement these two contents, just You can expand some of the capabilities you need that belong to the Spring framework itself.

In the phase of instantiation of the Bean object, we have operated some additional definitions, attributes, initialization and destruction operations. In fact, if we get Spring, such as BeanFactory and ApplicationContext, we can also implement it in this way. Then we need to define a marked interface, this interface does not need to have a method, it only serves as a marking function, and the specific function is defined by other functional interfaces that inherit this interface. Finally, this interface can pass instanceof Judgment and call are made. The overall design structure is as follows:

  • Define the interface Aware. In the Spring framework, it is a mark-aware interface. Specific subclass definitions and implementations can be aware of related objects in the container. also through this bridge to provide container services to specific implementation classes
  • The interfaces that inherit Aware include: BeanFactoryAware, BeanClassLoaderAware, BeanNameAware, and ApplicationContextAware. Of course, there are some other annotations in the Spring source code, but we still don't use them at present.
  • In the specific interface implementation process, you can see that part of ( BeanFactoryAware, BeanClassLoaderAware, BeanNameAware ) is in the support folder of the factory, and ApplicationContextAware is in the support of the context. This is because different content acquisition needs to be in different Provided under the package. ApplicationContextAwareProcessor operation of adding BeanPostProcessor content to beanFactory will be used in the specific implementation of AbstractApplicationContext, and finally the corresponding call operation will be processed when the createBean is created by AbstractAutowireCapableBeanFactory. About applyBeanPostProcessorsBeforeInitialization has been implemented in the previous chapter, if you forget, you can go forward

10. About the scope of the Bean object and the implementation and use of FactoryBean

Regarding providing a Bean object that allows users to define complex Beans, the function is very good, and the significance is very great, because after doing this, Spring's ecological seed incubator is provided, and anyone's framework can complete itself on this standard. Access to services.

But this kind of functional logic design is not complicated, because the entire Spring framework has provided with various expansion capabilities during the development process. You only need to provide a connection processing interface call and corresponding functions at the right place. The logic can be realized. The goal here is to provide a function that can obtain objects from FactoryBean's getObject method twice, so that all object classes that implement this interface can expand their object functions. MyBatis is to implement a MapperFactoryBean class, in the getObject method to provide SqlSession to perform CRUD method operations overall design structure as shown below:

  • The entire implementation process includes two parts, one is to solve the singleton or the prototype object, and the other is to deal with the getObject operation of obtaining the specific call object during the creation of the FactoryBean type object.
  • SCOPE_SINGLETON , SCOPE_PROTOTYPE , the creation and acquisition method of the object type, the main difference is AbstractAutowireCapableBeanFactory#createBean object is placed in the memory after the object is created. If it is not placed, it will be recreated every time it is acquired.
  • After createBean performs operations such as object creation, attribute filling, dependency loading, pre-post processing, initialization, etc., it is necessary to start execution to determine whether the entire object is a FactoryBean object. If it is such an object, you need to continue execution to obtain the FactoryBean. getObject object in the object is now. factory.isSingleton() will be added throughout the getBean process to determine whether to use memory to store object information.

11. Based on observer implementation, container events and event listeners

In fact, the design of the event itself is the realization of an observer mode. What it wants to solve is the problem of notifying other objects of an object state change, and it must take into account ease of use and low coupling to ensure a high degree of collaboration.

In terms of functional implementation, we need to define event classes, event listeners, and event releases. The functions of these classes need to be combined with Spring's AbstractApplicationContext#refresh() to facilitate the processing of event initialization and registration of event listeners. The overall design structure is as follows:

  • In the entire function implementation process, it is still necessary AbstractApplicationContext , including: initializing the event publisher, registering the event listener, and publishing the container refresh completion event.
  • Use the observer mode to define event classes, listener classes, and publish classes. At the same time, it is necessary to complete the function of a broadcaster. When an event push is received, the analysis and processing are in line with the event that the listener is interested in, that is, the isAssignableFrom is used for judgment.
  • isAssignableFrom is similar to instanceof, but isAssignableFrom is used to determine the relationship between the subclass and the parent class, or the relationship between the implementation class of the interface and the interface. By default, the ultimate parent class of all classes is Object. If the result of A.isAssignableFrom(B) is true, it proves that B can be converted into A, that is, A can be converted from B.

12. Based on JDK and Cglib dynamic proxy, realize the core function of AOP

Before the AOP design combines the entire section to Spring, we need to address two issues, including: How do agents, to methods conform to the rules of the case and finished proxy approach, to split up responsibilities like. The realization of these two function points is designed and developed with a cross-cutting idea. as cutting leeks with a knife. It’s always a bit slow to cut one by one. Then use your hand (160fe7299aab5d proxy) to squeeze the leeks into a handful, and use a kitchen knife or axe to intercept them Operation to deal with. The same is true in the program, except that leek has become a method, and the chopper has become an interception method. The overall design structure is as follows:

  • Just like you are using Spring's AOP, only handle some methods that need to be intercepted. After intercepting the method, perform your extension operations on the method.
  • Then we need to implement a proxy method that can proxy method first. In fact, the proxy method mainly uses the method interceptor class to process the method call MethodInterceptor#invoke , instead of directly using the method method in the invoke method to perform the method.invoke(targetObj, args) block. Time difference.
  • In addition to the implementation of the above core functions, we also need to use org.aspectj.weaver.tools.PointcutParser process the interception expression "execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))" . With the method proxy and processing interception, we can complete the design of a prototype of AOP.

13. Integrate AOP dynamic proxy into the life cycle of Bean

In fact, after the realization of the core functions of AOP, it is not difficult to integrate this part of the functional services into Spring. It just has to solve a few problems, including: how to integrate dynamic proxy into the life cycle of Bean through BeanPostProcessor, and How to assemble various pointcuts, intercepts, pre-functions and adapt the corresponding agents. The overall design structure is as follows:

  • In order to be able to instantiate the proxy objects configured in xml, that is, some class objects of the aspect, during the object creation process, you need to use the methods provided by BeanPostProcessor, because the methods in this class can be used to initialize the Bean object separately Modify the extended information of the Bean object before and after. But here needs to be assembled in BeanPostProcessor to implement new interfaces and implementation classes, so that the corresponding class information can be obtained directionally.
  • But because the proxy object created is not an ordinary object in the previous process, we need to precede the creation of other objects, so in the actual development process, it is necessary to complete the judgment of the Bean object first in AbstractAutowireCapableBeanFactory#createBean, whether it needs a proxy, If there is, the proxy object is returned directly. will have createBean and doCreateBean methods in the Spring source code to split
  • It also includes the specific functions of the method interceptor to be solved, and provides some implementations of BeforeAdvice and AfterAdvice, so that users can more simplify the use of aspect functions. In addition, it also includes the need to package aspect expressions and the integration of interception methods, as well as agency factories that provide different types of agency methods to package our aspect services.

Three, study instructions

This code repository https://github.com/fuzhengwei/small-spring to learn the Spring source code and learns the core principles of Spring by handwriting a simplified version of the Spring framework.

In the process of handwriting, the Spring source code will be simplified, the core logic in the overall framework will be extracted, the code implementation process will be simplified, and the core functions will be retained, such as: IOC, AOP, Bean life cycle, context, scope, resource processing and other content implementation.


  1. This column is for actual coding materials. In the process of learning, you need to combine the target to be solved in each chapter in the article, and the idea design , and bring it into the coding practice process. While learning to code, it is also best to understand why this part of the content is implemented in this way, what design patterns it uses, and what methods are used to achieve separation of duties. Only through such learning can you better understand and master the implementation process of Spring source code, and it can also help you lay a solid foundation in the process of in-depth study and practical application in the future.
  2. In addition, the study of the content of this column combines the design pattern , which corresponds to the SpringBoot middleware design and development , so if you encounter a design pattern you don’t understand during the learning process, you can read the corresponding information. After Spring, you can also practice with the content of the middleware.
  3. source code : The source code involved in this column has been integrated into the current project, and can be matched with the corresponding case source code in the chapter one by one. You can run the entire project directly, or you can open and run the source code project corresponding to each chapter separately.
  4. If you encounter any problems in the process of learning, including: inability to run, optimization comments, text errors, etc., you can submit an issue
  5. In the content of the column, each chapter provides clear design drawings and corresponding class diagrams, so in the learning process, you must not just care about how the code is written, and more importantly, understand how the content of these designs comes from of.

😁 Good, hope you can learn happily!


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。