Spring source code learning -- handwritten simulation of the underlying principles of spring

Posted by phpmady on Tue, 07 Sep 2021 21:24:27 +0200

Through handwriting simulation, understand the startup process of Spring's underlying source code, understand the concepts of BeanDefinition and BeanPostProcessor, understand the workflow of Spring parsing configuration class and other underlying source code, understand the workflow of dependency injection, Aware callback and other underlying source code through handwriting simulation, and understand the workflow of Spring AOP's underlying source code through handwriting simulation

Of course, the code implementation is very rough. The purpose is to better the loading process of Liao's spring underlying bean s

Project address: https://gitee.com/fanzitianxing/write-spring

Project directory

Note: This is a maven project. pom.xml in the project does not depend on any jar package. All annotation instances are defined and written by themselves

Related annotation classes:

@Retention(RetentionPolicy.RUNTIME) //The annotations are not only saved in the class file, but also exist after the jvm loads the class file
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Autowired { //This annotation is automatically injected by Spring

    boolean required() default true;
}


-----------------------------------------------------------------------------------------

@Retention(RetentionPolicy.RUNTIME)//The annotations are not only saved in the class file, but also exist after the jvm loads the class file
/**
 * @Target The annotation indicates the scope of use, that is, where the annotation can be placed
 * ElementType.TYPE            :  Interface, class, enumeration
 * ElementType.FIELD           :  Field, enumerated constant
 * ElementType.METHOD          :  method
 * ElementType.PARAMETER       :  Method parameters
 * ElementType.CONSTRUCTOR     :  Constructor
 * ElementType.LOCAL_VARIABLE  :  local variable
 * ElementType.ANNOTATION_TYPE :  annotation
 * ElementType.PACKAGE         :  package
 */
@Target(ElementType.TYPE)
/**
 * The @ Component annotation in spring is used to instantiate ordinary POJOs into the spring container, which is equivalent to the annotation in the configuration file
 * <bean id="" class=""/>)
 */
public @interface Component {
    String value() default "";
}

-----------------------------------------------------------------------------------------

@Retention(RetentionPolicy.RUNTIME)//The annotations are not only saved in the class file, but also exist after the jvm loads the class file
@Target(ElementType.TYPE)
/**
 * Corresponding to the @ ComponentScan annotation in spring, the function is to assemble the classes that meet the scanning rules into the spring container according to the defined scanning path
 */
public @interface ComponentScan {
    String value() default "";
}

----------------------------------------------------------------------------------------


@Retention(RetentionPolicy.RUNTIME)//The annotations are not only saved in the class file, but also exist after the jvm loads the class file
@Target(ElementType.TYPE)
/**
 * The @ Lazy annotation in spring is used to specify whether the bean is Lazy loaded
 *
 */
public @interface Lazy {
}

------------------------------------------------------
@Retention(RetentionPolicy.RUNTIME)//The annotations are not only saved in the class file, but also exist after the jvm loads the class file
@Target(ElementType.TYPE)
/**
 * The @ Scope annotation in the corresponding spring is used to specify the Scope of the bean, the basic Scope singleton (single instance), prototype (multiple instances), Web Scope (reqeust, session, globalsession), and user-defined Scope
 *
 */
public @interface Scope {
    String value() default "";
}

-----------------------------------------------------------

/**
 * @author fanzitianxing
 * @title: FztxValue
 * @projectName write-spring
 * @description: TODO
 * @date 2021/9/722:44
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FztxValue {
    String value() default "";
}

Related interface classes:

/**
 * Used for callback after instantiation
 */
public interface BeanNameAware {
    void setBeanName(String beanName);
}



----------------------------------------------------

/**
    *Bean Post processor: when initializing the bean, the Spring container will call back two methods in the BeanPostProcessor
    * @author fanzitianxing
    * @date 2021/9/7
    * @param
    * @return
    */
public interface BeanPostProcessor {

    /**
        *Before initialization
        * @author fanzitianxing
        * @date 2021/9/7
        * @param [bean, beanName]
        * @return java.lang.Object
        */
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    /**
        *After initialization
        * @author fanzitianxing
        * @date 2021/9/7
        * @param [bean, beanName]
        * @return java.lang.Object
        */
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

--------------------------------
public interface InitializingBean {
    void afterPropertiesSet();
}


What did Spring do when it started?

1) Scanning
When Spring starts, it will load the configuration class. Whether AppConfig specifies the scanning path range through the @ ComponentScan annotation, and scans the. Class file in the target directory, which is transformed into a BeanDefinition object, and finally added to the beanDefinitionMap. At the same time, if the implementation class of the BeanPostProcessor interface is stored in the bean postprocessor list, It is then used before and after initialization

(2) Instantiation
Instantiation is the Bean that creates an instance. Not all. Class files in the directory will be instantiated. Only non Lazy loaded singleton beans will be instantiated. If @ Scope("prototype") or @ Lazy annotation is added to a class, the object will not be instantiated at Spring startup, but only when the object is used

1. Instantiation
It means that all non lazy loaded singleton beans are obtained from the target directory, and in order that the subsequent processes do not follow the same instance process, each instance is put into a concurrenthashmap < string, beandefinition > collection and stored. key is the name of the bean and value is the definition class of the bean, which describes some information of the bean, such as whether it is a singleton or not, Whether it is lazy loading, instance type of bean, etc

2. Attribute filling
The BeanDefinition information is supplemented, and the injection dependency is involved here. If OrderService is injected into UserService through the annotation @ Autowired, it is first matched through ByType type and then found through ByName
Q: @ Autowired why ByType before ByName?
Solution: because the orderservice is annotated with @ Component("orderservice"), and the configured alias is orderservice, and the alias is obtained arbitrarily, other classes can also be called this. All instances in the concurrenthashmap < string, beandefinition > container may have the same name, but the corresponding instance Bean objects are different. If you look for them through ByName first, Multiple instance Bean objects may be matched
Add: @ Resource annotation is matched directly through ByName

3. Before initialization

Traverse the BeanPostProcessor interface implementation class list, execute the postProcessBeforeInitialization method, and do enhancement processing before initialization

4.Aware callback
Implement the setBeanName method in the BeanNameAware interface to obtain the alias of the Spring instance bean object

5. Initialization
The afterpropertieset method that implements the InitializingBean interface means that when a bean object instance is completed, check whether it is created successfully. For example, when Spring creates a UserService, it does not want the injected property object orderService to be empty, otherwise an exception will be thrown

5. Add to singleton pool
If the instance object is a singleton, it is added to the created singleton pool so that it does not need to be instantiated again for later use, so that it can be obtained directly from the singleton pool (it is simpler and easier to understand than that in the source code)

6. After initialization

  Traverse the BeanPostProcessor interface implementation class list, execute the postProcessAfterInitialization method, and do enhancement processing before initialization. springAop is implemented here

package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FztxApplicationContext {
    private Class configClass;
    //Each time a bean's definition information is created, it is saved. There may be multiple. class files under a file path. key is the name of the bean and value is the definition information of the bean
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    //The singleton pool holds all singleton bean s
    private Map<String,Object> singleObjects = new HashMap<>();
    //It is a list that stores the post processor of the bean
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * Spring Startup, two core steps
     * 1.Scan all classes under the specified path (scan the. class file under target)
     * 2.Create instance beans (automatic injection) -- Spring will only instantiate non lazy loaded singleton beans when it starts
     *
     * @param configClass
     */
    public FztxApplicationContext(Class configClass) {
        this.configClass = configClass;

        /**
         *  Scan the class to get the BeanDefinition (the attribute of the bean encapsulated inside)
         *  The path scan configured according to the @ ComponentScan("com.fztx.service") annotation can be multiple
         */
        scan(configClass);

        /**
         *  Instantiate the non lazy loading singleton bean, which is executed in five steps
         *  1.instantiation 
         *  2.Attribute filling
         *  3.Before initialization
         *  4.Aware Callback
         *  5.initialization
         *  6.After initialization
         *  7.Add to singleton pool
         */
        instanceSingletonBean();

    }

    /**
     * Instantiate non lazy load singleton bean
     */
    private void instanceSingletonBean() {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            //Determine whether it is a non lazy loading singleton bean
            if (beanDefinition.getScopeValue().equals("singleton")) {
                //Create singleton bean
                Object bean = doCreateBean(beanName, beanDefinition);
                //Put it into the singleton pool. When using the getbean method, the singleton bean can be directly fetched from the singleton pool
                singleObjects.put(beanName,bean);
            }
        }
    }

    /**
     * Create bean
     */
    private Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        //Create beans based on the bean definition, that is, BeanDefinition
        Class clazz = beanDefinition.getBeanClass();
        Object instance = null;
        try {
            /**
             * 1.Instantiate bean
             * The parameterless construction method is adopted here, and the inference construction method is not implemented
             */
            instance = clazz.getConstructor().newInstance();

            /**
             *  2.Attribute filling
             *
             */
            //Gets all properties in the instance bean
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //Judge whether there is @ Autowired annotation injection in the attribute
                if (field.isAnnotationPresent(Autowired.class)) {
                    //In addition, you can find it by byType first and byName later
                    //The problem of circular dependency will not be considered for the time being
                    String fieldName = field.getName();
                    Object bean = getBean(fieldName); //Get the bean directly by its name
                    field.setAccessible(true); //If the obtained field property is private, you must set true to access it, otherwise an error will be reported
                    field.set(instance, bean);
                }
            }

            /**
             *  Bean Post Processors 
             *  For example, attribute objects injected with @ Autowired and @ Resource annotations in UserService
             *       After the UserService bean is instantiated, process @ Autowired and @ Resource respectively
             *       Content of
             *  Spring The processing @ Autowired in the source code is Autowired annotation beanpostprocessor
             *                @Resource Is the CommonAnnotationBeanPostProcessor
             *
             */

            /**
             *  We can implement the BeanPostProcessor interface to realize our own functions
             *  bean The two methods of post processor can be extended to meet our requirements
             */

            /**
             *  3.Before initialization -- > traverse the post processor list of the bean and perform the operation before postProcessBeforeInitialization
             *
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }


            /**
             *  4.Aware Callback -- > determines whether the currently created instance bean implements the BeanNameAware callback interface
             *  Spring When scanning a class annotated with @ Component, a name will be assigned to the class (or configured in @ Component),
             *  At this time, you need to know what the corresponding name of the bean is, so the callback obtains the name of the bean
             */
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            /**
             *  5.Initialize and verify whether the bean created by spring is created successfully
             *  The execution order is placed after the instance bean, property filling and Aware callback
             */
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }


            /**
             *  5.Before initialization -- > traverse the bean's post processor list and perform post processafterinitialization
             *  aop It's here
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }



        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

    /**
     * Spring At startup, scan all. class files in the given path
     *
     * @param configClass
     */
    private void scan(Class configClass) {
        /**
         *  1.Scan all classes under the specified path (scan the. class file under target)
         *  Converted to BeanDefinition object, and finally added to beanDefinitionMap
         */
        //Get the scan path first
        if (configClass.isAnnotationPresent(ComponentScan.class)) { //Determine whether the @ ComponentScan annotation exists
            //If it exists, scan the class file according to the corresponding path according to the value of the annotation value
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();
            System.out.println("Spring Package path address to start scanning:" + path);
            //Scan the package path to get all. class files under the path
            List<Class> beanClasses = getBeanClasses(path);
            //Traverse beanClasses and encapsulate some attribute information of the bean in BeanDefinition,
            //So that when you get the bean s later, don't go through the process of scanning -- > instantiation again
            for (Class clazz : beanClasses) {
                //Judge whether the current bean is identified by the @ Component annotation. Only classes annotated with @ Component annotation can be loaded into the container
                if (clazz.isAnnotationPresent(Component.class)) {

                    //Add the post-processing logic of the Bean. When Spring scans, it will add all the implemented BeanPostProcessor interfaces to the post processor collection
                    if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                        BeanPostProcessor instance = null;
                        try {
                            instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                        beanPostProcessorList.add(instance);
                    }else{
                        //An instance bean is either automatically generated by Spring or obtained from the annotation @ Component (the annotation is not unique, here is only one example)
                        Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
                        //Gets the name of the bean identified in the annotation @ Component, for example, in the form of @ Component("userService")
                        String beanName = componentAnnotation.value();
                        //If the beanName is not set for the Component, spring will automatically generate the first letter in lowercase
                        if ("".equals(beanName)){
                            beanName = Introspector.decapitalize(clazz.getSimpleName());//spring bottom layer automatically generates beanname
                        }
                        //Create a bean definition object and set properties
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanClass(clazz);

                        //Judge whether there is @ Scope annotation
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scopeAnnotation = (Scope) clazz.getAnnotation(Scope.class);
                            //Gets the value of a singleton or prototype
                            String scopeValue = scopeAnnotation.value();
                            beanDefinition.setScopeValue(scopeValue);
                        }else{
                            //If not, the default is a single instance
                            beanDefinition.setScopeValue("singleton");
                        }

                        //Judge whether there is @ Lazy lazy loading annotation
                        if (clazz.isAnnotationPresent(Lazy.class)) {
                            beanDefinition.setLazyValue(true);
                        }

                        beanDefinitionMap.put(beanName,beanDefinition);
                    }

                }
            }


        }
    }

    /**
     * Gets the bean from the specified path
     * This class is obtained through independent simulation. It is relatively simple to write and easy to understand
     */
    private List<Class> getBeanClasses(String path) {
        List<Class> beanClasses = new ArrayList<>();
        //Get a class loader
        ClassLoader classLoader = FztxApplicationContext.class.getClassLoader();
        //Get the resources of the specified path according to the classloader, pass in the relative path, and get the folder file: / E: / Turing / code / write spring / target / classes / COM / fztx / service
        URL resource = classLoader.getResource(path.replace(".", "/"));
        System.out.println("Spring Scanned path address:" + resource);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                //Get the file path + name of class E: \ Turing \ code \ write spring \ target \ classes \ com \ fztx \ service \ userservice.class
                String absolutePath = f.getAbsolutePath();
                //Since there may be other files of non. class type under this folder, you need to judge
                if (absolutePath.endsWith(".class")) {
                    //classloader needs the package + name of class to load the class
                    //Get the corresponding class name of the. Class file
                    String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    //replace
                    className = className.replace("\\", ".");
                    //Get an object by loading the class loader
                    try {
                        Class<?> clazz = classLoader.loadClass(className);
                        beanClasses.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
        return beanClasses;
    }

    /**
     * Get Bean
     */
    public Object getBean(String beanName){

        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //Before creating a bean, judge whether it is a singleton,
        //If it is a singleton, directly check whether there is this instance bean in the singleton pool. If there is, directly take it out. If there is no new singleton bean, put it into the singleton pool
        if (beanDefinition.getScopeValue().equals("singleton")) {
            Object bean = singleObjects.get(beanName);
            if(bean == null){
                bean = doCreateBean(beanName, beanDefinition);
                singleObjects.put(beanName,bean);
            }
            return bean;

        }else{
            //Prototype bean, create a new bean according to beanDefinition
            Object bean = doCreateBean(beanName, beanDefinition);
            return bean;
        }

    }


}

Topics: Java Spring