Series article directory
Article directory
- Series Article Directory
- spring container
- Handwritten Spring container
-
- Pass in a Java configuration class
- @ComponentScan annotation class
- The Spring container obtains the scanning package path through the AppConfig configuration class
- Load all classes through the package path to obtain bean information
- Create a singleton bean through beanDefinitionMap
- @Autowired annotation class (circular dependencies are not considered here)
- implement initialization, before initialization, after initialization
- Aware mechanism
Spring container
IOC container is a container with dependency injection function, which is responsible for operations such as instantiation of objects, initialization of objects, configuration of dependencies between objects, destruction of objects, and search of externally provided objects. The entire life cycle of the object is controlled by the container. The objects we need to use are all managed by the ioc container. We don’t need to manually create objects through new. The ioc container will help us assemble them directly. When we need to use them, we can directly obtain them from the ioc container. up.
How does the spring ioc container know which objects need to be managed?
We need to provide a configuration list for the ioc container. This configuration supports xml format and java annotation. The objects that need to be managed by the ioc container are listed in the configuration file , and you can specify how to let the ioc container build these objects. When the spring container starts, it will load this configuration file, and then assemble these objects for external visitors to use.
The IOC container is also called the spring container.
What container implementations does Spring come with?
- The bean factory is the simplest container, providing basic DI support
- The application context is built on top of BeanFactory and provides application framework level services.
Because bean factories tend to be too low-level for most applications, application contexts are preferred over bean factories.
Create a Spring container
// Create a Spring container AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); //ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AnnotationConfigApplicationContext and ClassPathXmlApplicationContext are two common ways to create Spring containers. But the use of ClassPathXmlApplicationContext is actually outdated. AnnotationConfigApplicationContext is mainly used at the bottom of the new version of Spring MVC and Spring Boot. The usage of AnnotationConfigApplicationContext is very similar to ClassPathXmlApplicationContext, except that a class needs to be passed in instead of an xml file.
Handwritten Spring container
Pass in a Java configuration class
public class MyApplicationContext {<!-- --> private Class configClass; public MyApplicationContext(Class configClass){<!-- --> this.configClass = configClass; } }
At this time, you can pass in AppConfig.class through the MyApplicationContext construction method, and then get the scanning path through the @ComponentScan
annotation on the Java configuration class
@ComponentScan annotation class
@Retention(RetentionPolicy.RUNTIME) @Target(value = ElementType. TYPE) public @interface ComponentScan {<!-- --> String value() default ""; }
In this way, AppConfig.class can add the scan package path through the @ComponentScan
annotation
@ComponentScan("org.example.my.service") public class AppConfig {<!-- --> }
The Spring container obtains the scanning package path through the AppConfig configuration class
public class MyApplicationContext {<!-- --> private Class configClass; public MyApplicationContext(Class configClass){<!-- --> this.configClass = configClass; scan(); } public void scan(){<!-- --> if(this.configClass.isAnnotationPresent(ComponentScan.class)){<!-- --> ComponentScan componentScanAnnotation = (ComponentScan)configClass.getAnnotation(ComponentScan.class); String path = componentScanAnnotation. value(); } } }
Load all classes through the package path to obtain bean information
At this time, go to the target directory to find the class file under the path instead of the java file under src, you can use the getResource()
method of ClassLoader
public void scan(){<!-- --> if(this.configClass.isAnnotationPresent(ComponentScan.class)){<!-- --> ComponentScan componentScanAnnotation = (ComponentScan)configClass.getAnnotation(ComponentScan.class); String path = componentScanAnnotation. value(); path = path.replace(".","/"); ClassLoader classLoader = MyApplicationContext. class. getClassLoader(); URL resource = classLoader. getResource(path); System.out.println(resource); //file:/C:/Users/ZPJX/IdeaProjects/MySpring/target/classes/org/example/my/service } }
The absolute path of the scan package has been obtained here, and then the directory of the scan package is obtained through the absolute path, and then the class files under the scan package are traversed through the directory. After obtaining the class file, if you want to know whether the class file contains the @Component annotation, Spring uses AMS technology, and the easiest way now is to load the class file with the application class loader.
public void scan(){<!-- --> if(this.configClass.isAnnotationPresent(ComponentScan.class)){<!-- --> ComponentScan componentScanAnnotation = (ComponentScan)configClass.getAnnotation(ComponentScan.class); String path = componentScanAnnotation. value(); path = path.replace(".","/"); ClassLoader classLoader = MyApplicationContext. class. getClassLoader(); URL resource = classLoader. getResource(path); System.out.println(resource); //file:/C:/Users/ZPJX/IdeaProjects/MySpring/target/classes/org/example/my/service File file = new File(resource. getFile()); if (file.isDirectory()) {<!-- --> for (File f : file. listFiles()) {<!-- --> String absolutePath = f. getAbsolutePath(); absolutePath = absolutePath.substring(absolutePath.lastIndexOf("classes") + "classes".length() + 1,absolutePath.indexOf(".class")); absolutePath = absolutePath.replace("\","."); try {<!-- --> Class<?> clazz = classLoader. loadClass(absolutePath); if(clazz.isAnnotationPresent(Component.class)){<!-- --> // } } catch (ClassNotFoundException e) {<!-- --> throw new RuntimeException(e); } } } } }
After obtaining the class object of the file, if the class file has @Component annotation, then save the bean definition, that is, beanDefinition, into a Map.
if(clazz.isAnnotationPresent(Component.class)){<!-- --> //Take out the beanName Component componentAnnotation = (Component) clazz. getAnnotation(Component. class); String beanName = componentAnnotation. value(); if("".equals(beanName)){<!-- --> beanName = Introspector.decapitalize(clazz.getSimpleName()); } //Create beanDefinition BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setBeanClass(clazz); beanDefinition.setBeanName(beanName); if(clazz.isAnnotationPresent(Scope.class)){<!-- --> Scope scopeAnnotation = (Scope) clazz. getAnnotation(Scope. class); String scope = scopeAnnotation. value(); beanDefinition.setScopeType(scope); } else {<!-- --> //The default is a singleton bean beanDefinition.setScopeType("singleton"); } beanDefinitionMap.put(beanName, beanDefinition); }
Create a singleton bean through beanDefinitionMap
After scanning the package, create a singleton bean through beanDefinitionMap traversal
//Create a singleton bean for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {<!-- --> BeanDefinition beanDefinition = entry. getValue(); if(beanDefinition.getScopeType().equals("singleton")){<!-- --> Object o = createBean(beanDefinition); singletonMap. put(entry. getKey(), o); } } public Object createBean(BeanDefinition beanDefinition){<!-- --> Class clazz = beanDefinition. getBeanClass(); Object instance = null; try {<!-- --> instance = clazz.getConstructor().newInstance(); } catch (InstantiationException e) {<!-- --> throw new RuntimeException(e); } catch (IllegalAccessException e) {<!-- --> throw new RuntimeException(e); } catch (InvocationTargetException e) {<!-- --> throw new RuntimeException(e); } catch (NoSuchMethodException e) {<!-- --> throw new RuntimeException(e); } return instance; }
Then write the getBean(String beanName)
method
public Object getBean(String beanName){<!-- --> if(!beanDefinitionMap.containsKey(beanName)){<!-- --> throw new RuntimeException(); } BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); String scope = beanDefinition. getScopeType(); if("singleton".equals(scope)){<!-- --> Object singletonBean = singletonMap. get(beanName); return singletonBean; }else{<!-- --> Object prototypeBean = createBean(beanDefinition); return prototypeBean; } }
@Autowired annotation class (circular dependency is not considered here)
Then add the @Autowired
annotation. When creating an instance in createBean, you need to traverse all the fields in the class to determine whether the @Autowired
annotation is included. If so, give the instance The field in the bean object is injected. First find the bean by field type, if more than one is found, then find the bean by the field name.
public Object createBean(BeanDefinition beanDefinition){<!-- --> Class clazz = beanDefinition. getBeanClass(); Object instance = null; try {<!-- --> instance = clazz.getConstructor().newInstance(); for (Field field : clazz. getDeclaredFields()) {<!-- --> if (field.isAnnotationPresent(Autowired.class)) {<!-- --> field.setAccessible(true); Class type = field. getType(); List<String> beanNames = beanDefinitionMap.entrySet().stream().filter(new Predicate<Map.Entry<String, BeanDefinition>>() {<!-- --> @Override public boolean test(Map.Entry<String, BeanDefinition> entry) {<!-- --> BeanDefinition bean = entry. getValue(); if(bean.getBeanClass().equals(type)){<!-- --> return true; } return false; } }).map(e->e.getKey()).collect(Collectors.toList()); if(beanNames. size()==1){<!-- --> field.set(instance,getBean(beanNames.get(0))); } else {<!-- --> String name = field. getName(); field.set(instance,getBean(name)); } } } } catch (InstantiationException e) {<!-- --> throw new RuntimeException(e); } catch (IllegalAccessException e) {<!-- --> throw new RuntimeException(e); } catch (InvocationTargetException e) {<!-- --> throw new RuntimeException(e); } catch (NoSuchMethodException e) {<!-- --> throw new RuntimeException(e); } return instance; }
When the bean object is injected into the instance, the bean object may not be created yet. When the bean is a singleton, it cannot be found in the singletonMap and needs to be created.
Realize initialization, before initialization, after initialization
After the injection is completed, actions such as pre-initialization, initialization, and post-initialization begin
The initialization is relatively simple, implement an InitializingBean interface, let the bean implement the afterPropertiesSet()
method
public interface InitializingBean {<!-- --> void afterPropertiesSet() throws Exception; }
if(instance instanceof InitializingBean){<!-- --> ((InitializingBean) instance).afterPropertiesSet(); }
Both before and after initialization are related to BeanPostProcessor, you can customize a class to implement this interface, and add @Component
annotation to the class at the same time
public interface BeanPostProcessor {<!-- --> default Object postProcessBeforeInitialization(Object bean, String beanName){<!-- --> return bean; } default Object postProcessAfterInitialization(Object bean, String beanName){<!-- --> return bean; } }
When scanning the package, scan BeanPostProcessor and put it in the List
if(BeanPostProcessor.class.isAssignableFrom(clazz)){<!-- --> BeanPostProcessor instance = (BeanPostProcessor) clazz. getConstructor(). newInstance(); beanPostProcessorList.add(instance); continue; }
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName); } if(instance instanceof InitializingBean){<!-- --> ((InitializingBean) instance).afterPropertiesSet(); } for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName); }
beanPostProcessor can implement various operations of dynamic proxy, which is the implementation method of AOP, and implement aspect logic through beanPostProcessor.
Aware mechanism
Various Aware interfaces can also be implemented, such as the BeanNameAware interface.
if (instance instanceof BeanNameAware) {<!-- --> ((BeanNameAware) instance).setBeanName(beanName); }
A basic Spring underlying implementation is complete!