Spring IOC container startup process source code learning

1. Configuration class startup code:

@Configuration
@Import(value = {TulingService.class})
@ComponentScan(basePackages = "com.tuling.testspringiocstarter")
public class MainConfig {

    @Bean
    public TulingDataSource tulingDataSource() {
        return new TulingDataSource();
    }
}

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
        System.out.println(ctx.getBean(TulingDao.class));
    }
}

IOC container inheritance diagram

  It should be noted that our container class in Tomcat and other web environments is AnnotationConfigWebApplicationContext, which is under the spring web package

In the Spring container, beans are represented by BeanDefinition objects. Bean definition describes the configuration information of beans.
The BeanDefinitionRegistry interface provides methods to register, delete and obtain BeanDefinition objects from the container.

In short, BeanDefinitionRegistry can be used to manage BeanDefinition, so it is crucial to understand the AnnotationConfigApplicationContext. It is the most important class for spring to load beans and manage beans.
Source code tracking:

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		this();
		register(annotatedClasses);
		refresh();
}

1: this() calls the parameterless constructor  

public AnnotationConfigApplicationContext() {
        // Pre register some beanpostprocessors and beanfactorypost processors, which will be called in the next spring initialization process
		this.reader = new AnnotatedBeanDefinitionReader(this);
        // Scan the @ component annotated and derived classes in the specified directory, and then give the instances of these classes to BeanDefinitionReqistry
		this.scanner = new ClassPathBeanDefinitionScanner(this);
}

AnnotatedBeanDefinitionReader is initialized. The constructor is as follows

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
	this(registry, getOrCreateEnvironment(registry));
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
			
	//ConditionEvaluator completes the judgment of conditional annotation, which is widely used in the following Spring Boot
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	//This sentence will add some automatic annotation processors to the BeanDefinitions of BeanFactory under the AnnotationConfigApplicationContext, as shown below
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

Here, the AnnotationConfigApplicationContext is registered as the BeanDefinitionRegistry that manages BeanDefinition, that is, the management of beans in spring is completely left to the AnnotationConfigApplicationContext

AnnotationConfigUtils#registerAnnotationConfigProcessors()
We need to use some annotations, such as: @ Autowired/@Required/@Resource depends on a variety of beanpostprocessors to parse (Autowired annotation, required annotation, commonannotation BeanPostProcessor, etc.). It is obviously too heavy for the caller to use this very common one. Therefore, Spring provides us with a very convenient way to register these beanpostprocessors (if it is xml, configure < context: annotation - config / >. If it is full annotation driven ApplicationContext, it will be executed by default)

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
	registerAnnotationConfigProcessors(registry, null);
}

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
		BeanDefinitionRegistry registry, @Nullable Object source) {
	
	// Parse our beanFactory from the registry
	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
			beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
		}
		// This is equivalent to setting a context annotation AutowireCandidateResolver if there is no AutowireCandidateResolver
		if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
		}
	}
	
	//The initial length here is 4 because in most cases, we only register 4 beanpostprocessors as follows (I won't say more)
	// BeanDefinitionHolder explains: hold name and aliases to prepare for registration
	// After Spring 4.2, this is changed to 6, which I think is more accurate
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);

	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);
		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));
	}

	if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
	// Some annotations of JSR-250 are supported: @ Resource, @ PostConstruct, @ PreDestroy, etc
	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));
	}

	// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
	// If JPA support is imported, register the processor for JPA related annotations
	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));
	}


	// The following two classes are added after Spring 4.2 to provide support for better use of Spring events 
	// The @ EventListener annotation is supported. We can listen to events more conveniently through this annotation (after spring 4.2)
	// How does the Processor and ListenerFactory work? Listen to the event topic decomposition
	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));
	}

	return beanDefs;
}
  • ConfigurationClassPostProcessor is a beanfactoryprocessor and BeanDefinitionRegistryPostProcessor processor. The processing method of BeanDefinitionRegistryPostProcessor can process @ Configuration and other annotations. The ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry() method internally processes @ Configuration, @ Import, @ ImportResource and @ Bean inside the class.

The ConfigurationClassPostProcessor class inherits BeanDefinitionRegistryPostProcessor. The BeanDefinitionRegistryPostProcessor class inherits beanfactoryprocessor.
BeanDefinition registrypostprocessor allows you to create a special postprocessor to add BeanDefinition to BeanDefinition registry. It is different from the BeanPostProcessor. The BeanPostProcessor only has a hook for us to add some custom operations during Bean initialization; BeanDefinitionRegistryPostProcessor allows us to add some custom operations in BeanDefinition. In the integration of Mybatis and Spring, BeanDefinitionRegistryPostProcessor is used to post customize Mapper's BeanDefinition.

  • Autowired annotation beanpostprocessor is used to process @ Autowired annotation and @ Value annotation
  • Requiredannotation beanpostprocessor this is used to process @ Required annotations
  • Commonannotation beanpostprocessor provides support for JSR-250 specification annotations @ javax.annotation.Resource, @ javax.annotation.PostConstruct and @ javax.annotation.PreDestroy.
  • EventListenerMethodProcessor provides support for @ PersistenceContext.
  • EventListener methodprocessor provides @ EventListener support@ The EventListener has emerged since spring 4.2. You can use the @ EventListener annotation on a Bean method to automatically register an applicationlister.

Initialization of ClassPathBeanDefinitionScanner:

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
		Environment environment, @Nullable ResourceLoader resourceLoader) {

	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	this.registry = registry;
	
	//useDefaultFilters is true, so it is generally executed here
	// Of course, we can also set it to false. For example, it can be set to false in @ ComponentScan to scan only the specified annotations / classes, etc
	if (useDefaultFilters) {
		registerDefaultFilters();
	}
	// Setting environment
	setEnvironment(environment);
	// The details are as follows: here, the value is transferred by resourceLoader, or our factory. Otherwise null
	setResourceLoader(resourceLoader);
}


protected void registerDefaultFilters() {
	// Note that @ Component annotation is added by default
	//(equivalent to @ service @ controller @ repository, etc. will be scanned, because these annotations belong to @ Component) in addition, @ Configuration also belongs to Oh
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
	
	//The following two are JSR-250 compatible @ ManagedBean and 330 compatible @ Named annotations
	try {
		this.includeFilters.add(new AnnotationTypeFilter(
				((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
		logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
	} catch (ClassNotFoundException ex) {
		// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
	}
	try {
		this.includeFilters.add(new AnnotationTypeFilter(
				((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
		logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
	} catch (ClassNotFoundException ex) {
		// JSR-330 API not available - simply skip.
	}
	
	// Therefore, if you want Spring to scan your customized annotations, you can implement an annotation type filter yourself
}

  ClassPathBeanDefinitionScanner inherits from ClassPathScanningCandidateComponentProvider. It internally maintains two final type lists: these two objects will work when executing the scancandiatecomponents() method of this class.

//The in includeFilters is the one that satisfies the filtering rules
private final List<TypeFilter> includeFilters = new LinkedList<>();
//excludeFilters does not meet the filtering rules
private final List<TypeFilter> excludeFilters = new LinkedList<>();

2: register(annotatedClasses)

public void register(Class<?>... annotatedClasses) {
	Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
	this.reader.register(annotatedClasses);
}

public void register(Class<?>... annotatedClasses) {
	for (Class<?> annotatedClass : annotatedClasses) {
		registerBean(annotatedClass);
	}
}

public void registerBean(Class<?> annotatedClass) {
	registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
}

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    // First convert this entity type to a BeanDefinition
	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    //abd.getMetadata() metadata includes annotation information, internal Class, Class basic information, etc
	// conditionEvaluator#shouldSkip filters whether this Class is a configuration Class.
	// The general logic is: there must be @ Configuration modification. Then parse some Condition annotations to see if they are excluded~
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}

    // Resolve Scope
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
    // The name of the obtained Bean is generally lowercase (here is the AnnotationBeanNameGenerator)
	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    // Set default values for some annotations, such as lazy, Primary, and so on
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    // Resolve qualifiers. If there is this annotation, the primary becomes true
	if (qualifiers != null) {
		for (Class<? extends Annotation> qualifier : qualifiers) {
			if (Primary.class == qualifier) {
				abd.setPrimary(true);
			}
			else if (Lazy.class == qualifier) {
				abd.setLazyInit(true);
			}
			else {
				abd.addQualifier(new AutowireCandidateQualifier(qualifier));
			}
		}
	}

    // The following bit resolves whether the Scope needs an agent, and finally registers the Bean
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

3: refresh()    Container startup core method

2. How to start packet scanning

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.tuling.testspringiocstarter");
    System.out.println(ctx.getBean(TulingDao.class));
}

public AnnotationConfigApplicationContext(String... basePackages) {
	this();
	scan(basePackages);
	refresh();
}

public void scan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	// The core is in the scan method, as shown below
	this.scanner.scan(basePackages);
}

/**
 * Perform a scan within the specified base packages.
 * @param basePackages the packages to check for annotated classes
 * @return number of beans registered Number of bean s successfully registered
 */
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

Focus on the doScan() method:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	// Load scanned beans
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {

		// This method is the most important, and the scanned beans are included (compared with this, only RootConfig has a Bean definition, which is a configuration class)
		// This is the key point. All beans under the package will be scanned. Spring 5 is not handled in the same way as the following~
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			// Get the Scope metadata: singleton here
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			// The name of the generated Bean. The default is lowercase. Here is "rootConfig"
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

			// This is the scanned Bean and the ScannedGenericBeanDefinition, so it must be true
			// Therefore, come in and execute postProcessBeanDefinition (for Bean definition information), as detailed below
			// Note: only add some default Bean definition information, not execute post processor~~~
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			// Obviously, this is also true and perfect. For example, some annotation information on beans: for example, @ Lazy, @ Primary, @ DependsOn, @ Role, @ description @ Role annotations are used for the classification and grouping of beans, which is not very useful
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			// Check the Bean, for example
			//If the class under dao package (generally configured basePakage) meets the requirements of mybaits, register its BeanDefinition in the spring IOC container. Therefore, it is necessary to check the third-party Bean in this step
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				
				//The applyScopedProxyMode method of the AnnotationConfigUtils class applies the corresponding proxy pattern to the Bean definition according to the value of the Scope @ Scope annotation configured in the annotation Bean definition class, which is mainly used in Spring aspect oriented programming (AOP)
				definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);

				// Note: Bean has been registered in the factory here. All doScan() methods do not receive return values, and there is no problem....
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

Findcandidate components: scan candidate components according to the basePackage (very important)

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
	try {
		// 1. Generate a package search path according to the specified package name
		//By observing the implementation of the resolveBasePackage() method, we can use placeholders like ${} when setting the basePackage. Spring will replace them here as long as they are in the environment
		// The current value is: classpath*:com/fsx/config/**/*.class
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		//2. The Resource loader loads all class es under the search path and converts them to Resource []
		// With the above path, you can get all. class classes from getResources. This is very powerful~~~
		// The real solution is the PathMatchingResourcePatternResolver#getResources method
		// Two classes AppConfig (ordinary class without any annotation) and RootConfig can be scanned here. So the next step is to parse the annotations on the class and filter out classes that are not candidates (such as AppConfig)
		
		// Note: there may be hundreds of. Class files under the class path (excluding those in the jar package), and then they will be handed over to the later for filtering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Of course, it has something to do with getResourcePatternResolver and this template
		Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

		// Record the log (I'll delete all the places where the log is printed below)
		boolean traceEnabled = logger.isTraceEnabled();
		boolean debugEnabled = logger.isDebugEnabled();
		for (Resource resource : resources) {
			if (traceEnabled) {
				logger.trace("Scanning " + resource);
			}
			if (resource.isReadable()) {
				try {
					//Read class annotation information and class information, and store the two information in MetadataReader
					MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
					// Filter and exclude components according to TypeFilter. Because AppConfig has no standard @ Component or sub annotation, it certainly does not belong to the candidate Component, and false is returned
					// Note: here, generally (in the case of default processing) the default annotation will be true. What is the default annotation? Is @ Component or derived annotation. And javax... Is omitted here
					if (isCandidateComponent(metadataReader)) {
						//Convert the qualified class to BeanDefinition
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						sbd.setResource(resource);
						sbd.setSource(resource);
						// Again, judge whether it returns true if it is an entity class or an abstract class, but the abstract method is returned true by the @ Lookup annotation (note that this and the above are overloaded methods) 
						if (isCandidateComponent(sbd)) {
							if (debugEnabled) {
								logger.debug("Identified candidate component class: " + resource);
							}
							candidates.add(sbd);
						}
					}
				}
			}
		}
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
	}
	return candidates;
}

ClassPathBeanDefinitionScanner#postProcessBeanDefinition

protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
	// Bean definition setting default information
	beanDefinition.applyDefaults(this.beanDefinitionDefaults);

	if (this.autowireCandidatePatterns != null) {
		beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
	}
}

In this way, all the scanned beans are registered (if you have more configs, it's easier to scan ~)

Keywords: Java Spring

Added by Cleanselol on Mon, 20 Sep 2021 07:02:05 +0300