Spring's IoC Source Analysis (Annotation-based)

Posted by Devious Designs on Mon, 19 Aug 2019 05:52:14 +0200

I. IoC Theory

IoC is called Inversion of Control, translated as "Control Inversion", and it also has a nickname DI (Dependency Injection), which is Dependency Injection.

II. IoC Mode

Spring provides two ways for IoC, one based on xml and the other based on annotations.

  • The < bean > tag defines and manages beans.
  • @ bean annotations define beans and manage them.

In this article, we will analyze the IoC principle based on annotations. Before reading this article, we can bring some questions, which will help us better understand.

  1. @ What is Bean for?
  2. @ What are Controller and @Service?
  3. @ How does the CompoentScan annotation work?
  4. How did Spring find annotated classes like @Bean, @Controller, @Service?
  5. How to register in IOC container after discovery?
  6. What exactly is an IOC container?

3. Source code analysis

First, look at the following code:

AnnotationConfigApplicationContext aac =
				new AnnotationConfigApplicationContext("com.mydemo");

Annotation Config Application Context enables Java-based configuration classes (including various annotations) to load Spring's application context. Avoid using application.xml for configuration. It is more convenient than XML configuration.

3.1. Class Structure Diagram

Main class or interface description:

  • Generic Application Context - General application context with a DefaultListableBeanFactory instance inside. This class implements the BeanDefinitionRegistry interface and can use any bean definition reader on it. Typical use cases are: registering bean definitions through the BeanFactoryRegistry interface, then calling refresh() method to initialize beans with application context semantics (org. spring framework. context. ApplicationContextAware), automatically detecting org. spring framework. beans. factory. config. BeanFactoryPostProcessor And so on.

  • BeanDefinition Registry -- A registry interface for holding bean definitions like RootBeanDefinition and ChildBeanDefinition instances. DefaultListableBeanFactory implements this interface, so beans can be registered in the bean Factory by corresponding methods. Generic Application Context has a DefaultListableBeanFactory instance built in. Its implementation of the interface is actually achieved by calling the corresponding method of the instance.

  • AbstractApplicationContext - An Abstract implementation of the ApplicationContext interface, which does not impose configuration storage types, implements only general context functions. This implementation uses template method design pattern and requires specific subclasses to implement its abstract method. Automatically registering BeanFactoryPostProcessor through registerBeanPostProcessors() method, BeanPostProcessor and ApplicationListener instances are used to detect special beans in bean factory --- Contrast 1 Analysis

  • Annotation Config Registry - Annotation configuration registry. A common interface for annotation configuration application context has a method for registering configuration classes and scanning configuration classes.

3.2 constructor

        //The default constructor initializes an empty container that does not contain any Bean information and needs to be called register() later.
	//Method registers the configuration class and calls refresh() method to refresh the container, triggering the loading, parsing and registration of annotation beans by the container
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	
	public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
		super(beanFactory);
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	
	//The most commonly used constructor is to automatically register beans in corresponding configuration classes into containers by passing the involved configuration classes to the constructor.
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		//Initialize AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner by calling a parametric constructor
		this();
		register(annotatedClasses);
		refresh();
	}

	
	//This constructor automatically scans all classes under a given package and its subpackages, and automatically identifies all Spring Bean s and registers them in containers.
	public AnnotationConfigApplicationContext(String... basePackages) {
	        //Initialize ClassPathBean Definition Scanner and AnnotatedBean Definition Reader
		this();//step1
		//Scanning packages, registering bean s
		scan(basePackages);//step2
	        refresh();//step3
	}

Main attributes:

  • Annotated bean Definition Reader - bean Definition parser for parsing annotated beans

  • ClassPathBean Definition Scanner - The scanner for bean s scans classes

  • Register and parse incoming configuration classes (parse using class configuration)

  • Initialize the container by calling the refresh method of the container

Here we use the last constructor, which is to pass in a packet path.

3.3 IoC Constructor Initialization

First, look at step 1 and call the parametric constructor of this class:

public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

Then initialize Annotated Bean Definition Reader and ClassPathBean Definition Scanner
Let's look at the constructor of ClassPathBean Definition Scanner

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
		this(registry, true);
	}

Continue tracking, and finally call this method:

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

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		//Setting up registers defined by loading beans for containers
		this.registry = registry;

		//Whether to use default filtering rules
		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		//Setting up environment
		setEnvironment(environment);
		//Setting up resource loaders for containers
		setResourceLoader(resourceLoader);
	}

The main one here is the registerDefaultFilters() method, which initializes the spring scan default filtering rules, corresponding to the @ComponentScan annotation, and initializes the default filtering rules if there are no custom rules.
The registerDefaultFilters() method in the ClassPathScanning CandidateComponentProvider class is called here:

//Register filtering rules with containers
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
	//Add @Component annotation class to the filter rules to be included
	//@ Both Service and @Controller are Components, because these annotations add @Component annotations
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	//Get the class loader for the current class
	ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
	try {
		//Add the @ManagedBean annotation for JavaEE6 to the filtering rules to be included
		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 {
		//Add the @Named annotation to the filtering rules to be included
		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.
	}
}

There are two key variables:

  • private final List<TypeFilter> includeFilters = new LinkedList<>();

  • private final List<TypeFilter> excludeFilters = new LinkedList<>();

IncludeFilters represent annotations to be included, that is, only annotations in includeFilters are scanned.
ExcludeFilters indicate annotations to be excluded, that is, annotations containing excludeFilters will not be scanned

In this method, the @Component, the @ManagedBean of JavaEE6, and the @Named annotation of JSR-330 are added to the includeFilters collection.
The excludeFilters collection does not make any changes, that is, there are no annotations to exclude.

Summary:
So the default rule is that any of the three annotations @Component, JavaEE6 @ManagedBean, and JSR-330 @Named will be scanned.

Topics: Programming Spring xml Java