Zero Preparations
0 FBI WARNING
The article is extremely verbose and winding.
1 version
Spring version: spring 5.1.2.RELEASE
IDE : idea 2018.3
2 Bean Demo
package ioc; /** * java bean */ public class Person { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
3 Config Demo
package ioc; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Configuration class */ @Configuration public class IocConfig { /** * Injecting a bean in code configuration */ @Bean(name = "person") public Person getPerson(){ Person person = new Person(); person.setAge(100); person.setName("Zhang San"); return person; } }
4 main method
package ioc; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class IocTest { public static void main(String[] args){ ApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class); Person person = (Person) context.getBean("person"); System.out.println(person.getName() + " , " + person.getAge()); } }
Initiation of a project and injection of beans
1 Overview
Spring's initialization is encapsulated in this line of code:
ApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class);
There are three lines of code inside this constructor method:
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { this(); // 2 parametric constructor register(annotatedClasses); // 3 reader registration configuration class refresh(); // 4 Create beans in bean factory }
2 parametric constructor
The starting point of this part is that Annotation Config Application Context calls its own parametric constructor:
//AnnotationConfigApplicationContext.class public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { this(); // 2 parametric constructor register(annotatedClasses); refresh(); }
Tracking parametric constructors:
//AnnotationConfigApplicationContext.class public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); //2.1 this.scanner = new ClassPathBeanDefinitionScanner(this); //2.5 }
Annotated Bean Definition Reader registers Spring's own required beans and configuration beans into bean factory during its creation.
[ClassPathBean Definition Scanner is not used for the time being in this example. Without detailed analysis, just take a brief look at the construction method]
The parent Generic Application Context of Annotation Config Application Context creates a DefaultListableBeanFactory in the parametric constructor, which is the bean factory in this case:
//GenericApplicationContext.class public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); }
Within DefaultListableBeanFactory, several Concurrent HashMap objects are maintained to store bean s in categories.
The most important map object is singletonObjects. This object is defined in DefaultSingleton bean Registry to store all singleton beans:
//DefaultSingletonBeanRegistry.class private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
2.1
In the process of creating Annotated Bean Definition Reader, several processor bean s required by spring are registered in DefaultListable Bean Factory. The main processes are as follows:
//AnnotatedBeanDefinitionReader.class public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) { //Call another parametric constructor of itself //The registry here is the Annotation Config Application Context itself //The getOrCreateEnvironment(registry) here eventually returns an object created by calling the Standard Environment parametric constructor. this(registry, getOrCreateEnvironment(registry)); } //AnnotatedBeanDefinitionReader.class public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { //Parametric non-air validity Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); //Save Annotation Config Application Context this.registry = registry; //Create a comment parser, which you will use later this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); //Register the processor bean s that Spring needs AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
Environment, as its name implies, is Spring's environment, including the read of properties configuration, etc. This example is not used for the time being, press the table.
The core of the above code is to call the registerAnnotationConfigProcessors(...) method of AnnotationConfigUtils to continue tracking:
//AnnotationConfigUtils.class public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) { registerAnnotationConfigProcessors(registry, null); } //AnnotationConfigUtils.class public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { //Get the bean factory DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); //The following code sets up two internal objects of the bean Factory and does not expand for the time being if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } //Create a new collection to return the wrapper class of all registered bean s at the end of the method, but in fact, in this case, no return value is received, so it can be ignored Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); //To determine whether a bean with this name exists in a bean factory, register a bean if it does not exist. The following are the same if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { //Package the registered processor class as a BeanDefinition RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); //In this case, source is null def.setSource(source); //Register bean s and add them to collections beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } //Both jsr250Present and jpaPresent are boolean variables //jsr250Present means checking whether the JSR-250 standard is supported, and jpaPresent means checking whether the JPA standard is supported. //Spring currently supports JSR-250 and JPA standards. if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); }catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } //Returns the above collection return beanDefs; }
In this method, the main body code is the same, that is:
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { //Package the registered processor class as a BeanDefinition RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); //In this case, source is null def.setSource(source); //Register bean s and add them to collections beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); }
CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalConfigurationAnnotationProcessor CONFIGURATION_BEAN_NAME_GENERATOR : org.springframework.context.annotation.internalConfigurationBeanNameGenerator AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalAutowiredAnnotationProcessor COMMON_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalCommonAnnotationProcessor PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME : org.springframework.context.annotation.internalPersistenceAnnotationProcessor PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME : org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor EVENT_LISTENER_PROCESSOR_BEAN_NAME : org.springframework.context.event.internalEventListenerProcessor EVENT_LISTENER_FACTORY_BEAN_NAME : org.springframework.context.event.internalEventListenerFactory
These constants are the paths and names of classes in spring.
registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)
This line of code calls the bean DefinitionMap object inside DefaultListableBeanFactory through Annotation Config Application Context, and finally queries whether the bean object has been registered. If it does not exist, the registration operation below is performed.
beanDefinitionMap is a Concurrent HashMap object that stores information about all registered bean s in DefaultListableBeanFactory, which is often mentioned later.
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
RootBeanDefinition is the implementation class of BeanDefinition. BeanDefinition is a descriptive wrapper interface for beans in Spring. It is used to store all kinds of information about beans, including and not limited to the bean's name, parent class, annotations, lazy loading and other attributes.
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
BeanDefinition Holder is a support class for BeanDefinition, which stores BeanDefinition, bean name, and bean aliases (aliases).
[In Spring's bean registration process, there are many packaging and unpacking operations of Bean Definition and Bean Definition Holder, which need to be studied.)
registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)
The main function is the above line of code. Tracking the implementation of this method:
//AnnotationConfigUtils.class private static BeanDefinitionHolder registerPostProcessor( BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) { //Role Definition of Beans //BeanDefinition.ROLE_INFRASTRUCTURE = 2 definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); //Call the registerBeanDefinition method of AnnotationConfigApplicationContext registry.registerBeanDefinition(beanName, definition); //Return a wrapper class return new BeanDefinitionHolder(definition, beanName); }
The role of BeanDefinition refers to the role definition of the bean in Spring. For Spring registered processor bean s, the definition values are both 2, meaning infrastructure.
In addition, role definitions include application and support.
There is no registerBeanDefinition(...) method inside the Annotation Config Application Context, but it inherits from Generic Application Context:
//GenericApplicationContext.class public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { //2.2 this.beanFactory.registerBeanDefinition(beanName, beanDefinition); }
This method actually calls the registerBeanDefinition(...) method of DefaultListableBeanFactory for bean registration.
2.2
Continue to track the internal implementation of the method:
//DefaultListableBeanFactory.class public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { //Non-null validation of parameters Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); //Validation of bean s if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //beanDefinitionMap is a map object that stores all registered bean information //Normally, the null value should be retrieved here, i.e., the bean's information is not registered yet. BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { //If the bean already exists in the map, you need to determine whether it is allowed to override the bean definition if (!isAllowBeanDefinitionOverriding()) { //No, throw an exception throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); }else if (existingDefinition.getRole() < beanDefinition.getRole()) { if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } }else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } }else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } //As long as it is set to allow rewriting, the map will be updated at the end, except that the log s recorded will be different. this.beanDefinitionMap.put(beanName, beanDefinition); }else { //Normally if (hasBeanCreationStarted()) { //2.3 synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; if (this.manualSingletonNames.contains(beanName)) { Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); updatedSingletons.remove(beanName); this.manualSingletonNames = updatedSingletons; } } }else { //Register beans in bean Definition Map this.beanDefinitionMap.put(beanName, beanDefinition); //Add bean name to bean Definition Names this.beanDefinitionNames.add(beanName); //Delete bean name from manualSingleton Names this.manualSingletonNames.remove(beanName); } //Empty array this.frozenBeanDefinitionNames = null; } //2.4 if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }
First, look at this line of code:
((AbstractBeanDefinition) beanDefinition).validate();
Track this line of code:
//AbstractBeanDefinition.class public void validate() throws BeanDefinitionValidationException { if (hasMethodOverrides() && getFactoryMethodName() != null) { throw new BeanDefinitionValidationException( "Cannot combine static factory method with method overrides: " + "the static factory method must create the instance"); } if (hasBeanClass()) { //This method is also mainly used for hasMethod Overrides (), which is ignored for the time being. prepareMethodOverrides(); } }
This method uses the hasMethod Overrides () and getFactoryMethodName() methods defined in AbstractBeanDefinition to determine whether the bean Definition is valid. If these two conditions are met at the same time, an exception will be thrown.
According to official documentation, hasMethodOverrides() returns true if the bean factory overrides the method in the bean; getFactoryMethodName() returns the name of the method if the factory method exists.
There is no special understanding of the application scenarios of these methods. According to the online data, there should be some relationship with the reflection creation of bean s.
Then look at this line of code:
this.frozenBeanDefinitionNames = null;
FrozenBean Definition Names is an array of strings defined in DefaultListable Bean Factory. Spring converts the bean Definition Names into an array after bean registration and assigns it to FrozenBean Definition Names.
[This is a memory optimization operation for Spring]
2.3
Look at the fragments of the above methods:
if (hasBeanCreationStarted()) { synchronized (this.beanDefinitionMap) { //thread lock //Perform registration this.beanDefinitionMap.put(beanName, beanDefinition); //Replace the original with a new list List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; //Check the collection of singleton bean names if (this.manualSingletonNames.contains(beanName)) { Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); updatedSingletons.remove(beanName); this.manualSingletonNames = updatedSingletons; } } }
Let's first trace the internal implementation of the hasBeanCreationStarted() method:
//AbstractBeanFactory.class protected boolean hasBeanCreationStarted() { return !this.alreadyCreated.isEmpty(); }
alreadyCreated is a collection defined in AbstractBeanFactory to store the names of all beans that have been created (not registered, but created). The collection is checked when beans are created and retrieved.
In the registration phase of processors bean and iocConfig bean, the collection is empty, and this method returns false. Note, however, that during the registration phase of the person bean, the iocConfig bean has been created, so the collection is not empty, and this method returns true.
//Create a new list List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); //The old list is added to the new list as a whole updatedDefinitions.addAll(this.beanDefinitionNames); //Add beanName to the new list updatedDefinitions.add(beanName); //replace this.beanDefinitionNames = updatedDefinitions;
bean Definition Names is a list defined in DefaultListable BeanFactory to store the names of all registered beans.
if (this.manualSingletonNames.contains(beanName)) { //Create a new collection and put the manual Singleton Names collection into the new collection Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); //Delete the bean name updatedSingletons.remove(beanName); //replace this.manualSingletonNames = updatedSingletons; }
Manual Singleton Names is a collection defined in DefaultListable BeanFactory to hold the name of a single bean that has been created. Because it is singleton, the existence of renamed beans is not allowed, which may be the reason why Spring uses collections here.
Spring's memory optimization is accurate to the size of each list and collection's capacity.
In the case of this if criterion, it must be that the bean has not been created, so if the bean has been placed in this collection, it needs to be deleted. Normally, the bean registration process should not go into executing this code fragment.
2.4
Look at the fragments of the above methods:
if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); }
The existingDefinition in the judgment condition is a BeanDefinition defined above:
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
Generally speaking, it's equal to null.
The containsSingleton(...) method is implemented in DefaultSingleton Bean Registry:
//DefaultSingletonBeanRegistry.class public boolean containsSingleton(String beanName) { return this.singletonObjects.containsKey(beanName); }
As mentioned in the previous article, singletonObjects are the map object that ultimately saves the singleton bean.
To sum up, this judgment condition can be understood as: if the bean has been registered or created, it returns true, and if none has been created, it returns false. So the normal registration process does not perform the resetBeanDefinition(...) method.
As for the resetBeanDefinition(...) method, let's not expand on it. Generally speaking, this method will recreate the bean internally.
So far, reader has been created and processors used by Spring have been registered. The DefaultListableBeanFactory.registerBeanDefinition method is very important, which is also used for the registration of config bean s and person bean s.
2.5
Back to the origin:
//AnnotationConfigApplicationContext.class public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); //2.4 }
ClassPathBean Definition Scanner is not used in this example for the time being, because there is no way to scan the package path to get the bean. So here's a brief introduction to the creation of ClassPathBean Definition Scanner.
Its construction method has multiple invocations, and the final logical code is as follows:
//ClassPathBeanDefinitionScanner.class public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { //Parametric non-air validity Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); //The registry here is Annotation Config Application Context this.registry = registry; //Filtering strategy of Spring scanning package path //useDefaultFilters = true if (useDefaultFilters) { registerDefaultFilters(); } //Store the environment object, where a Standard Environment object is created using a parametric constructor setEnvironment(environment); //Save in resourceLoader, here Annotation Config Application Context setResourceLoader(resourceLoader); }
[Not for the time being]