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