A preface.
The Spring family is so large that it takes a lot of effort for developers to fully conquer it. As the saying goes, a snake is beaten by seven inches, so what are the “seven inches” of the Spring family? The answer in my mind has always been the Spring Framework!
In this article, I document a little source code reading and combing through my own learning of the Spring Framework to talk about how the Spring container scans beans during startup.
Ii. Learning methodology
I believe that every developer who wants to be a good developer wants to understand Spring source code, and SO do I. So there are many ways to find Spring source learning materials, buy books, watch videos and so on. In the end, I found that only by settling down and following the source code debugging step by step, line by line, can I deeply understand the secrets of Spring! It’s a tedious process, but a good hunter can stand loneliness and restlessness best!
As we know, the Spring container can be started in a variety of ways: XML files, annotations, Java Config. In the actual use is not to choose one of them, but with each other. The underlying container startup process is the same, but the entry point has changed. In addition, the best way to learn Spring is to build your own source code project so that it is easy to read, annotate, and modify. The resulting project looks like this:
Code entry
Don’t say a word and get to work! The code entry is as follows:
@Configuration
@ComponentScan("com.leon.funddatahouse")
public class Config {}public class MyApplication {
public static void main(String[] args) {
// Our annotation-based approach
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
// If the configuration is based on an XML file, it can also be as follows:
// ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");}}Copy the code
In the constructor, three things are done. These three things include the entire process of starting the Spring container! Gnawing them is half the battle!
public AnnotationConfigApplicationContext(Class
... componentClasses) {
// 1. Call the default constructor to prepare the container environment
this(a);// 2. Register related information based on the configuration class
register(componentClasses);
// 3. Refresh the entire container
refresh();
}
Copy the code
Four. Before parsing
The UML class diagrams for the container and BeanFactory are exposed before parsing. The reason is that they have too many roles, too many functions and too much power, and they make the source code harder to understand. So here first released UML class diagram as a manual view, easy to understand the source.
4.1 Container UML class diagram
4.2 BeanFactoryUML class diagram
Five. Source code analysis
5.1 Construction method analysis
5.1.1 Initializing the BeanFactory in a Container
In the constructor, this() is explicitly called, with no arguments:
public AnnotationConfigApplicationContext(a) {
// 1. Instantiate reader
this.reader = new AnnotatedBeanDefinitionReader(this);
// 2. Instantiate the container scanner
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
Copy the code
At first glance, this no-argument constructor does two things, but it doesn’t. It is actually equivalent to:
public AnnotationConfigApplicationContext(a) {
// 1. Call the parent constructor
super(a);// 2. Instantiate reader. More on this later
this.reader = new AnnotatedBeanDefinitionReader(this);
// 3. Instantiate scanner. In the container
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
Copy the code
This is critical, because if you don’t realize that the parent constructor is called implicitly, you can’t go any further because you’re doing a big thing in the parent constructor:
// In the constructor of the parent class, the BeanFactory in the container is created. At this point, the container has the property created by the first program :beanFactory
public GenericApplicationContext(a) {
/ / initialize the beanFactory container, type of DefaultListableBeanFactory
this.beanFactory = new DefaultListableBeanFactory();
}
Copy the code
For the difference between BeanFactory and FacotryBean, click here
5.1.2 Instantiating a Reader in a container
The primary purpose of reader is to assist in registering BeanDefinitions. The details of its use are described below, but we only need to know what it contains.
// Enter registry as the container itself. As you can see from the UML class diagram above, the container inherits BeanDefinitionRegistry indirectly
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
// The getOrCreateEnvironment() method gets the environment. The actual type is actually the StandardEnvironment class by default. The environment is twofold:
// 1. SystemEnvironment: operating systemEnvironment. In this way, Spring can get data about the operating system itself, the number of CPU cores, and so on.
// 2. SystemProperties: JVM environment variables. In this way, Spring can retrieve the underlying data of the JVM, such as the environment variables that we set manually in the startup parameters.
this(registry, getOrCreateEnvironment(registry));
}
Copy the code
Another constructor inside the reader is called via this() :
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
/ / set the registry, already know that it is the container itself: AnnotationConfigApplicationContext
this.registry = registry;
// Create a condition handler
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
// Critical! Register the necessary post-processors in the container ahead of time
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
Copy the code
This constructor is important because it involves two important members of the Spring container: the conditional parser and the post-processor!
5.1.2.1 Instantiate the condition handler
Those of you familiar with Spring know or have used ConditionalOnBean / @conditionalonClass. The parse of these annotations is ConditionEvaluator.
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
ConditionContextImpl is actually delegated to the inner ConditionContextImpl class
this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}
// ------------ splitter ------------------
// The inner ConditionContextImpl constructor
public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
/ / again, the actual type is AnnotationConfigApplicationCont registry
this.registry = registry;
/ / get the beanFactory, we also know the beanFactory ConfigurableListableBeanFactory namely actually
this.beanFactory = deduceBeanFactory(registry);
// Get the environment from the container. As described earlier, the encapsulation class for the environment in the container is StandardEnvironment
this.environment = (environment ! =null ? environment : deduceEnvironment(registry));
// Resource loader. As you can see from the UML class diagram,resourceLoader is the container because the container indirectly inherits resourceLoader
this.resourceLoader = (resourceLoader ! =null ? resourceLoader : deduceResourceLoader(registry));
// Class loader. It's essentially a classloader that gets the beanFactory. As it should be, the class loaders in the container must be consistent
this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
}
Copy the code
ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator: ConditionEvaluator
5.1.2.2 Register some backend processors
Once ConditionEvaluator has been initialized, the next step is particularly important as it is here that some post-handlers are injected ahead of time:
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
// Empty shell methods that actually delegate to overloaded methods
registerAnnotationConfigProcessors(registry, null);
}
Copy the code
The method of reloading is as follows (high energy warning):
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// Get the beanFactory from the container. So it goes into the if branch
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if(beanFactory ! =null) {
// The beanFactory attribute dependencyComparator is null.
DependencyComparator = dependencyComparator = dependencyComparator
/ / AnnotationAwareOrderComparator inherited OrderComparator,
// Therefore, classes that implement the Ordered interface and annotate @order or @priority can be Ordered.
// That is, set the orderComparator in beanFactory to support the sorting capability of the parse beans.
if(! (beanFactory.getDependencyComparator()instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
/ / the beanFactory initialization, the default is SimpleAutowireCandidateResolver, so come in for the first time the judge must also set up here.
The main role is to support / / ContextAnnotationAutowireCandidateResolver @ Lazy annotation class processing.
if(! (beanFactory.getAutowireCandidateResolver()instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(newContextAnnotationAutowireCandidateResolver()); }}// Initialize a BDH container to hold the BD of the post-processor to be parsed next.
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
// When the container is initialized for the first time, there is no bd in it.
That is, from here, the container will load BDS for the first time, and these BDS are spring's built-in post-processors.
/ / get and rear registered ConfigurationClassPostProcessor processor of bd
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));
}
/ / get and rear registered AutowiredAnnotationBeanPostProcessor processor of bd
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));
}
/ / get and rear registered CommonAnnotationBeanPostProcessor processor of bd
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));
}
/ / get and rear registered PersistenceAnnotationBeanPostProcessor processor of bd
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));
}
/ / get and rear registered EventListenerMethodProcessor processor of bd
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));
}
/ / get and rear registered DefaultEventListenerFactory processor of bd
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;
}
Copy the code
This method first introduced the BeanDefinition class. Spring’s BeanDefinition is equivalent to Java’s Class
After this method, the above 6 BDS exist in beanFactory:Someone once told me that if you master Spring’s back processor, you’ve mastered 10% of Spring! You can see its importance.
But instead of expanding the post-processor (there are too many), the main thread of this article is the container startup process.
5.1.2.3 Reader Initialization Process Summary
This is where the initialization of the Reader part is finally done. To sum up, reader initialization does these things: 1. Create and set the Environment property in the container. The default is the StandardEnvironment class. 2. Create and set ConditionEvaluator in the container, which is actually delegated to the inner ConditionContextImpl class. 3. Register six post-processors into the container. Note that only the BeanDefinition of the post-processor is generated. Bean parsing and post-processing have not yet been performed.
5.1.3 Instantiating the Scanner in the container
After parsing Reader, continue parsing scanner. The scanner is the actual type ClassPathBeanDefinitionScanner. Its primary purpose is to scan all class files in the classpath for bd resolution. Its final call constructor is as follows:
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
// 1. Delegate to another constructor inside
this(registry, true);
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
// 2. Delegate to another constructor inside >_<
// We can see from the above entry that Registry is actually the container itself and uses the default filter. What does this filter do
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment) {
// 3. Delegate to another internal constructor T^T
this(registry, useDefaultFilters, environment,
(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
// 4. Finally (not easy) ^_^
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// Again, registry is the container!
this.registry = registry;
/ / important!!!!!! Whether to include default filters. As you can see from the input above, useDefaultFilters = true, so it goes into the if branch
if (useDefaultFilters) {
registerDefaultFilters();
}
// Set environment variables
setEnvironment(environment);
// Set the resource loader
setResourceLoader(resourceLoader);
}
Copy the code
5.1.3.1 registerDefaultFilters () method
As we know from the final constructor, the Scanner uses a filtering policy during scanning and uses the default filtering policy. The default policy is this method of resolution.
protected void registerDefaultFilters(a) {
// Scan the class of the @Component annotation
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
// Scan all @manageBean classes
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
try {
// Scan all @named classes
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
}
Copy the code
One thing: @manageBean and @named do the same thing as @Component. It’s just that we’re used to @Component.
Why not add a default scan for @service, @repository, @Controller? The reason is simple: these annotations inherit @Component indirectly. At this point, scanner has parsed, and the main thing it does is add the default filter policy so that it can scan for the @Component annotated classes later on.
Summary of default constructors
Now let’s look at the constructor again:
public AnnotationConfigApplicationContext(Class
... componentClasses) {
// 1. Call the default constructor to prepare the container environment
this(a); register(componentClasses); refresh(); }Copy the code
From the entrance, there are only three lines of code, But the first line, which calls the default constructor, does so much preparation, and brings up some of the most important components in Spring’s architecture, such as BeanFactory/BeanDefinition/BeanDefinitionReader / BeanDefinitionScanner/Environment/ConditionEveluator/PostProcessor, etc. You could drink a whole pot from one of them! These points will be broken down, but not the focus of this article, which will focus on the first step of the startup process: the execution of the constructor.