Handwritten spring startup and scanning logic implementation

During the startup of the spring container, the class file under the specified package path will be scanned to determine whether the current class is a bean object. If it is a bean object, it will be injected into the spring container. This time, let's implement the scanning logic in the spring startup process.

1. Basic configuration information

The information of AppConfig configuration class is as follows:

@ComponentScan("com.zhouyu.service")
public class AppConfig {

}

Among them, @ ComponentScan is an annotation class defined by ourselves, which is mainly used to define the package scanning path. During the container startup process, spring will obtain the scanning path defined by @ ComponentScan by reading the class object of the configuration class AppConfig class.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    // Path to scan
    String value();
}

@Component is a common annotation in spring. Adding @ Component annotation to a class indicates that the class is a Bean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    // Defined bean name. If the bean name is not defined, spring will use the lowercase initial of the class name as the bean name
    String value() default "";
}

The @ Component annotation is added to the UserServiceImpl class. This class will be scanned during spring startup (the scanning logic is later).

@Component("userService")
public class UserServiceImpl implements UserService {

@Scope defines the scope of the bean, whether it is a singleton or a prototype

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    // Define the scope of the bean
    String value();
}

2. Packet scanning logic

The information of spring startup class is as follows:

public class Test {
    public static void main(String[] args) {
        ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);

      	...
    }
}

First, create a new zhouyuapplicationcontext under the spring package Java, as a simulation implementation of spring container.

// Configure the class of the class
private Class configClass;

public ZhouyuApplicationContext(Class configClass) {
  this.configClass = configClass;

  // Resolve configuration class
  // Resolve @ componentscan -- > scan path -- > scan -- > beandefinition -- > beandefinitionmap
  scan(configClass);

  ...
}

The ZhouyuApplicationContext Class provides a Class configClass attribute and a constructor with a Class parameter. A Config configuration Class is passed on the spring startup Class as the configuration information of the spring startup Class.

The scan method completes the packet scanning logic. Through the class object of the passed in AppConfig class, we can read the @ ComponentScan annotation on the AppConfig class, which defines the path of package scanning.

ComponentScan componentScanAnnotation =(ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();//com.zhouyu.service

In Java, classes need to be added and loaded into the JVM. Different classes are loaded by different class loaders. The Bootstrap class loader loads classes under the jre/lib directory, the Ext class loader loads classes under the jre/ext/lib directory, and the App class loader loads classes under the classpath directory. Where classpath is our project path.

The author's computer is a Mac operating system. The file path separators of Mac operating system and Windows operating system are different, which should be paid attention to when implementing.

private void scan(Class configClass) {
  ComponentScan componentScanAnnotation =(ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
  String path = componentScanAnnotation.value();//com.zhouyu.service

  // Scan to load classes into the JVM through the class loader
  // Bootstrap--->jre/lib
  // Ext--------->jre/ext/lib
  // App--------->classpath
  ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
  // The classloader loads the resources of the relative path
  URL resource = classLoader.getResource(path.replaceAll("\\.", "/"));
  //        System.out.println(resource.getFile());
  // Convert to File, easy to operate
  File file = new File(resource.getFile());

  if (file.isDirectory()) {
    File[] files = file.listFiles();
    for (File file1 : files) {
      ///Users/liuxin/Desktop/Demo/lx-spring/target/classes/com/zhouyu/service/UserService.class
      String absolutePath = file1.getAbsolutePath();
      if (absolutePath.endsWith(".class")) {
        String substring = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));

        // Gets the fully qualified name of the class
        String className = substring.replaceAll("/", "\\.");//com.zhouyu.service.UserService

        try {
          Class<?> clazz = classLoader.loadClass(className);

           // If @ Component is added to the class, the bean is generated to the spring container
           if (clazz.isAnnotationPresent(Component.class)) {
             ...
           }

          }
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      }

    }
  }
}

During spring scanning, the stored bean definition is not the stored bean object. BeanDefinition is the definition of a bean object, which stores the Class of the bean, the scope (singleton or prototype) of the bean and other information for us to create a bean object.

public class BeanDefinition {

    private Class clazz;

    private String scope;

    public BeanDefinition(Class clazz, String scope) {
        this.clazz = clazz;
        this.scope = scope;
    }

    public BeanDefinition() {
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

In the ZhouyuApplicationContext container, an attribute is also defined to store the BeanDefinition

// beanDefinitionMap stores the beandefinition scanned when spring starts
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

Generate beanDefinition, set beanClass and scope attributes, and add them to beanDefinitionMap

// Determine whether to generate beans and inject them into the spring container according to the scope of the beans
// Resolve class - > beandefinition
// BeanDefinition has an attribute indicating whether the bean is a singleton or a prototype
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();

BeanDefinition beanDefinition = new BeanDefinition();
// Set clazz
beanDefinition.setClazz(clazz);
// Set scope
if (clazz.isAnnotationPresent(Scope.class)) {
    Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
    beanDefinition.setScope(scopeAnnotation.value());
} else {
    // The default setting is single instance
    beanDefinition.setScope("singleton");
}
// Add beanDefinition to beanDefinitionMap
beanDefinitionMap.put(beanName,beanDefinition);

3. singletonObjects of single instance pool

Spring defines a singleton pool to store singleton beans. After the spring scan logic scan method is executed, spring will inject the non lazy loaded singleton beans into the spring container

// Singleton pool, storing singleton bean map < beanname, bean Object >
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

// When the spring container starts, load the singleton bean into the spring container
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
    String beanName = entry.getKey();
    BeanDefinition beanDefinition = entry.getValue();
    if ("singleton".equals(beanDefinition.getScope())) {
        Object bean = createBean(beanName,beanDefinition);
        singletonObjects.put(beanName,bean);
    }
}

Get the bean object in spring through the getBean method. It supports obtaining the bean object through the class and name of the bean. When obtaining the bean, it will also judge the scope of the bean. If it is a single instance, it will be obtained directly from the single instance pool. If it is multiple instances, it will create the bean through the createBean method. In the implementation, get the bean object through beanName.

public Object getBean(String beanName) {
  if (beanDefinitionMap.containsKey(beanName)) {
    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    if ("singleton".equals(beanDefinition.getScope())) {
      Object o = singletonObjects.get(beanName);
      return o;
    } else {
      // Prototype, create bean return
      return createBean(beanName,beanDefinition);
    }
  } else {
    throw new RuntimeException("Nonexistent beanName");
  }
}

spring creates bean objects through the createBean method

/**
 * Singleton and prototype bean s are created through this method
 * @param beanDefinition
 * @return
 */
public Object createBean(String beanName,BeanDefinition beanDefinition) {
  Class clazz = beanDefinition.getClazz();
  try {
    // instantiation 
    Object instance = clazz.getDeclaredConstructor().newInstance();

    ...

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

Refer to station B video: link

Keywords: Java Spring Back-end

Added by chandru_cp on Mon, 27 Dec 2021 21:14:51 +0200