This article starts with WeChat official account of vivo Internet technology.
Link: https://mp.weixin.qq.com/s/NvPO5-FWLiOlrsOf4wLaJA
By Shi Zhengxing
As a very popular open-source framework in Java field, SpringBoot integrates a large number of commonly used third-party library configurations. In Spring Boot applications, these third-party libraries can almost be used out of the box with zero configuration. Most Spring Boot applications only need a very small amount of configuration code, and developers can focus on business logic more. Spring Boot is quick to start, but if you need some special customization of business scenarios in your project, or even customization of the source code, then it is necessary to understand the principle. Only when you fully understand the source code and know the working principle of the bottom layer of the framework, can you modify / extend the original mechanism of the source code and so on. This article introduces how SpringBoot parses configuration classes and integrates third-party configuration.
1, Introduction to basic concepts
In spring boot, Java Config is recommended to replace the traditional XML method to introduce beans. This paper analyzes how spring boot parses these configuration classes, injects our customized and spring boot provided beans into the container. The spring boot version is based on 2.1.7.RELEASE.
// Usually a spring boot project contains such a main configuration class, which is located under the root package of our project. By starting this main method, we can start our project // Next, we will analyze the function of @ SpringBootApplication annotation. In the second section, we will analyze the run method. In the run method, we will analyze the configuration class @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
// Click @ SpringBootApplication to find that it is actually composed of three core annotations, which are explained below @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
1. @ SpringBootConfiguration annotation
// Click here to find that it is actually a @ Configuration annotation. When spring boot parses it, it will know that this is a Configuration class, and it will introduce some bean s to the container // A class marked by @ Configuration, equivalent to a applicationContext.xml file // @Configuration is actually a @ Component annotation @Configuration public @interface SpringBootConfiguration { }
2. @ EnableAutoConfiguration annotation
// Combined with the following @ AutoConfigurationPackage annotation, it is found that the @ EnableAutoConfiguration annotation introduces two bean s to the container through the @ Import annotation, // AutoConfigurationImportSelector and autoc onfigurationPackages.Registrar Through these two classes, more classes can be introduced into the container // Next, I will introduce the use of @ Import annotation @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { } @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
@Import annotation
In the native spring framework, there are three ways to assemble components
- Use @ Component annotation, spring 2.5+
- Use Configuration classes @ Configuration and @ Bean, Spring3.0+
- Using modules to assemble @ EnableXXX and @ Import, Spring3.1+
If you want to register more beans, it is not convenient to use 1) 2) two methods. You can use the module assembly function provided by Spring. You can complete the effect of component assembly by annotating @ Enable for the configuration class and @ Import for the annotation. Next, you can explain the use of @ EnableXXX and @ Import through an example.
// Step 1) // Create entity classes of several animals, such as Cat, Bird, Chicken, Dog, Duck, Pig, Sheep, Snake, Tiger // The bean s are then injected into the container through various uses of @ Import @Data public class Cat { private String name; }
// Step 2) add our customized @ EnableAnimal annotation to the main configuration class, which means that the above animal beans will be imported into the container through @ EnableAnimal // @EnableAnimal can be compared with the @ EnableAutoConfiguration annotation we mentioned above. The two functions are similar @SpringBootApplication @EnableAnimal public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
// Step 3) // @EnableAnimal is shown below. This is our custom annotation. The core of this annotation is to declare the @ Import annotation on it // @The Import annotation can pass in four types: normal class, configuration class, implementation class of ImportSelector, and implementation class of importbeandefinitionregister // Here are four types of usage of @ Import annotation Import @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import() public @interface EnableAnimal { }//1) direct @ Import(), there will be the bean of dog in the container after the container is started. Note that the class of dog is not injected through @ Component
// 2) @Import () can also be imported in this form of import configuration class @Configuration public class AnimalRegisterConfiguration { @Bean public Cat cat(){ return new Cat("cat"); } }
// 3) @Import(), by returning a Spring array, you can easily specify multiple beans in the array // The above AutoConfigurationImportSelector is just this way. Spring boot imports MybatisAutoConfiguration to the container // Automatic configuration classes are imported in this way. In Section 2, configuration class parsing will talk about this public class AnimalImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] ; } }
// 4) @Import(), inject Bean into registry by manual encoding // We're talking about autoc onfigurationPackages.Registrar That's how it works public class AnimalImportBeanDefinitionRegistar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { registry.registerBeanDefinition("snake", new RootBeanDefinition(Snake.class)); } }
3. @ ComponentScan annotation
This annotation is equivalent to that in XML< context:component-scan >It will scan the classes identified with @ Controller, @ Service, @ Repository, @ Component annotation from the defined scan package path (the default is the package and its subpackages where the Spring boot master configuration is located) to the Spring container.
// We can see that the @ ComponentScan annotation specifies two Filter filter conditions. It is an extension mechanism provided by SpringBoot, which enables us to // Register some custom component filters with IOC container to filter some beans during package scanning @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// This is the method of TypeExcludeFilter. You can see that it is to get all typeexcludefilters in the container and traverse them to filter some unwanted beans public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) { Collection<typeexcludefilter> delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values(); for (TypeExcludeFilter delegate : delegates) { if (delegate.match(metadataReader, metadataReaderFactory)) { return true; } } } return false; }
// We can customize a TypeExcludeFilter. Its function is to filter out the User bean and prevent it from being injected into the container @Component public class MyTypeExcludeFilter extends TypeExcludeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return User.class.getName().equals(metadataReader.getClassMetadata().getClassName()); } } @Component public class User { private String name; }
// You can see that AutoConfigurationExcludeFilter filters the autoconfiguration class, because the autoconfiguration class xxxAutoConfiguration is passed by // The above AutoConfigurationImportSelector is imported. It is unnecessary to import it twice public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware { public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); } }
4. @ Conditional comment
We say that the spring boot convention is larger than the configuration. It imports some components into the container through some xxxAutoConfiguration. If you need but do not configure the view parser, spring boot will provide its default view parser, which simplifies the configuration. If you define a view parser yourself, which one will spring boot inject into the container? Look at the default injected view parser code below, and find that there is a @ ConditionalOnMissingBean annotation on it, which means that if there is no such view parser in the container, the container will inject you one. If there is one in the container, it will not inject it.
// @ConditionalOnMissingBean is an implementation class through @ Conditional annotation and Condition interface( OnBeanCondition.class )To achieve this effect @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() {
@Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean {
@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
We can also implement a custom condition annotation ourselves.
// Define the implementation class MyCondition of a Condition interface, and judge whether the specified conditions are met through its matches method public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // Get the value corresponding to the value specified in the annotation myconditionannotation. If there is no such value, the condition is not met String[] value = (String[]) metadata.getAnnotationAttributes(MyConditionAnotation.class.getName()).get("value"); for (String property : value){ if(StringUtils.isEmpty(context.getEnvironment().getProperty(property))){ return false; } } return true; } }
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(MyCondition.class) public @interface MyConditionAnotation { String[] value() default {}; }
// Inject A type A Bean into the container through @ Component, provided that the values corresponding to key1 and key2 specified by @ myconditionannotation annotation are configured @Component @MyConditionAnotation({"key1", "key2"}) public class A { }
5,SpringFactoriesLoader
Similar to Java's SPI and Dubbo's SPI mechanism, SpringBoot also provides a mechanism by reading META-INF/spring.factories Files (these files may exist in multiple jar packages in the classpath) to load some preconfigured classes, and the core mechanism comes from spring factors loader. spring.factories The file must be in the form of properties, where key is the fully qualified name of the interface or abstract class, and value is the comma separated list of fully qualified class names of the implementation class.
// Let's take a look at the AutoConfigurationImportSelector described in the @ Import annotation above. We say that we can Import spring boot to provide those // The following is the selectImport method of AutoConfigurationImportSelector described in the Import annotation @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // Ignore some other code // The meaning here is to load meta-inf through spring factors loader/ spring.factories Those xxxAutoConfiguration configured in // And put it into the String array to return AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } // Ignore the intermediate procedure call, which will be analyzed later. Next, go here and load the auto configuration class through springfactoryesloader protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // See the following analysis for specific calls List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
public static List<string> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryClassName = factoryClass.getName(); // Get a map in the form of < key, list < string > & gt;. getOrDefault // org.springframework.boot.autoconfigure.EnableAutoConfiguration Corresponding implementation class set // Note that not only the automatic configuration classes are loaded, but also the implementation classes configured by the listener and initializer are loaded. Here, they are uniformly loaded and put into the cache return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } // Meta-inf with cache function from each jar package/ spring.factories The implementation class is loaded in the file. A key may contain multiple implementations private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { // Direct return in cache return result; } try { // FACTORIES_RESOURCE_LOCATION value is META-INF/spring.factories Enumeration<url> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { // Get key String factoryClassName = ((String) entry.getKey()).trim(); // Separate value commas to obtain specific implementation classes for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { // Put it into result result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); // See the figure below for the returned result return result; } }
You can see that many auto configuration classes are returned, including aopaconfiguration, RabbitAutoConfiguration, and mybatisautoconfiguration (not truncated in the figure)
6,BeanFactoryPostProcessor
- BeanFactoryPostProcessor, BeanFactory postprocessor, is an extension point for BeanFactory. Spring will load beandefinition after BeanFactory initialization. However, beandefinition can be modified or added before bean creation.
- BeanDefinitionRegistryPostProcessor, a sub interface of BeanFactoryPostProcessor, is an extension point for BeanFactory, that is, Spring will call it before calling BeanFactoryPostProcessor. We will focus on the following
- ConfigurationClassPostProcessor is the implementation class of the interface. SpringBoot uses it to parse configuration classes and encapsulate them into BeanDefinition containers.
- BeanPostProcessor is an extension point for Bean, that is, Spring will process Bean before and after Bean initialization, and AOP and dependency injection are implemented through BeanPostProcessor.
The following is a customized BeanFactoryPostProcessor and BeanPostProcessor. It is found that BeanFactoryPostProcessor can add a new Bean to the container or modify the original Bean definition. BeanPostProcessor can modify the property values of the created Bean.
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; // Add a BeanDefinition to the container GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(Chicken.class); registry.registerBeanDefinition("beanFactoryPostProcessor-Chicken", beanDefinition); // Modify the original BeanDefinition in the container BeanDefinition snake = registry.getBeanDefinition("snake"); snake.setLazyInit(true); } }
@Component public class CatBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Cat){ Cat cat = (Cat) bean; cat.setName("changeNameCat"); } return bean; } }
2, Overview of SpringBoot startup process
The first section is some knowledge points that will be used in the spring boot parsing auto configuration class. Let's see the specific process of spring boot parsing configuration class. The above figure is the spring boot startup flowchart. In step 5 of the refreshContext, the postProcessBeanDefinitionRegistry method of the container's BeanFactoryPostProcessor will be called. One of them is the ConfigurationClassPostProcessor, which is set into the container when the ConfigurableApplicationContext is created, as shown below.
// To create a ConfigurableApplicationContext, the default is to create a normal Servlet Web container, which is the following // Create by reflection will go to its default constructor public AnnotationConfigServletWebServerApplicationContext() { // If you go in here, you will go to the following code this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } // Come here registerAnnotationConfigProcessors(registry, null); // Come here // Inject a ConfigurationClassPostProcessor into the container, which is BeanFactoryPostProcessor 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)); } // Inject an Autowired annotation BeanPostProcessor into the container, which is used to solve dependency injection 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)); }
3, Configuration class resolution
As mentioned above in step 5 of refreshcontext, BeanFactoryPostProcessor in the container will be called
The postProcessBeanDefinitionRegistry method of. One of them is ConfigurationClassPostProcessor, which is the entry for us to parse the automatic configuration class. Next, we will analyze its postProcessBeanDefinitionRegistry method.
1. Overview of configuration class resolution process
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { // Delete some non critical code processConfigBeanDefinitions(registry); }
The following processConfigBeanDefinitions method corresponds to steps 1, 2, 3, 4, and 5 in the above figure. Steps 4 and 5 are relatively long, which are separated for analysis.
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<beandefinitionholder> configCandidates = new ArrayList<>(); // Get the registered bean names in the container, as shown in the figure below. Note that these bean definitions in the container are added by the container during initialization // It's not the bean definition of our business code. This code is actually coherent. It's separated for the convenience of annotation pictures String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { // Get BeanDefinition BeanDefinition beanDef = registry.getBeanDefinition(beanName); // Determine whether the configurationClass property of this BeanDefinition is full or lite. If it is considered that it has been processed, it will be empty by default the first time, // Go down the branch if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { // Print log record } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 1) Next, we will analyze the checkConfigurationClassCandidate method. From the method name, we can guess whether the class is a configuration class // Configuration class means that it will introduce bean s into the container. This method mainly depends on whether there is @ configuration annotation in the class's meta information // Do you have @ Component annotation, @ ComponentScan, @ Import, @ ImportResource annotation, and @ Bean method // Construct a BeanDefinitionHolder and put it into configCandidates configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found // In the above figure, seven bean definitions have been introduced by default. Through the above detection, it is found that by default, there is a qualified configuration class, namely our main configuration class // configCandidates is one of them, which is spring boot application if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable // sort configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Delete some codes // Parse each @Configuration class // Configuration class resolution tool ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // Pending set Set<beandefinitionholder> candidates = new LinkedHashSet<>(configCandidates); // Collections processed Set<configurationclass> alreadyParsed = new HashSet<>(configCandidates.size()); // Cycle until candidates.isEmpty() do { // Start parsing here, corresponding to step 4 parser.parse(candidates); parser.validate(); // Take out some configuration classes set from step 4 Set<configurationclass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Delete some codes // The bean definition will also be loaded here, corresponding to step 5 in the figure this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); } while (!candidates.isEmpty()); }
2. Check whether it is a configuration class
In the Configuration class resolution flow chart, the second step is to get the registered beandefinitions in the container, put them into the candidateNames, and then traverse these beandefinitions in turn to determine whether they have been processed. If they have been processed, it doesn't matter. Otherwise, the checkConfigurationClassCandidate method is used to determine whether they are Configuration classes. By reading this code, we find that if a class has @ Configuration annotation, or @ Component, @ ComponentScan, @ Import, @ ImportResource annotation, or @ Bean annotation, it is considered a Configuration class. By default, when we get here, only one candidateName will match. It is our main Configuration class, that is, spring bootapplication Bean.
ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory); public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName(); // Gets the name of the next class. If the class name is empty or the class is a factory class if (className == null || beanDef.getFactoryMethodName() != null) { return false; } // Get metadata information of class AnnotationMetadata metadata; // Only one of the seven candidate names in the above figure is annotatebeandefinition, and the rest return false if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) { // Can reuse the pre-parsed metadata from the given BeanDefinition... // springbootApplication comes here metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata(); } else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); metadata = new StandardAnnotationMetadata(beanClass, true); } else { try { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); // Read the metadata information of the class, including annotations and other information metadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { return false; } } // metadata.isAnnotated(Configuration.class.getName()), this is to determine whether there is @ configuration annotation on the class if (isFullConfigurationCandidate(metadata)) { // If this property is set to true, it will be marked as processed beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (isLiteConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { // The remaining six return false return false; } // It's a full or lite configuration candidate... Let's determine the order value, if any. Integer order = getOrder(metadata); if (order != null) { // Get @ Order information on the following class beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; }
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? for (String indicator : candidateIndicators) { // Judge whether there are any comments on the next class if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... try { // Determine if @ Bean is available return metadata.hasAnnotatedMethods(Bean.class.getName()); } return false; } } private static final Set<string> candidateIndicators = new HashSet<>(); static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); }
3. Step 4 Analysis
public void parse(Set<beandefinitionholder> configCandidates) { // Delete part of the code. configCandidates here is the main configuration class represented by springBootApplication for (BeanDefinitionHolder holder : configCandidates) { // Get BeanDefinition BeanDefinition bd = holder.getBeanDefinition(); // Our SpringBootApplication will come this way. Let's analyze this first parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // Pay attention to (* *). The DeferredImportSelector will be processed here. The AutoConfigurationImportSelector we mentioned earlier is processed here // Import xxxAutoConfiguration to container this.deferredImportSelectorHandler.process(); } protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // Determine whether to resolve this configuration class according to the conditions marked by the @ Conditional annotation on the current class if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // Use configClass as the key to obtain. You can't get it for the first time. Follow the logic below ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. // In fact, there is nothing to be done in this step. The focus is on the next step SourceClass sourceClass = asSourceClass(configClass); do { // Here is the key point, which is divided into 8 steps. Take a separate section for analysis // b) doProcessConfigurationClass sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); // Put it into the configuration classes this.configurationClasses.put(configClass, configClass); }
// The above asSourceClass actually encapsulates a SourceClass object public SourceClass(Object source) { this.source = source; if (source instanceof Class) { this.metadata = new StandardAnnotationMetadata((Class<?>) source, true); } else { this.metadata = ((MetadataReader) source).getAnnotationMetadata(); } }
The following doprocess configuration class is divided into eight small steps to analyze, corresponding to four A-H steps
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // Determine whether there is @ Component annotation on this class if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first // If there is, traverse its inner class, and then call doProcessConfigurationClass to recursively process processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations // Handle the PropertySource annotation, which has been analyzed in the previous explanation of property configuration, that is, load the property file corresponding to the annotation into the Environment for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } } // Process any @ComponentScan annotations // Process the @ ComponentScan annotation to register the bean s under the package specified in it into the framework Set<annotationattributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<beandefinitionholder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations // Handling Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations // Handle @ ImportResource annotation. You can specify xml file through it. BeanFactory will read the xml file and register the bean AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods // Process the @ Bean annotated method in our class and add it to the Bean method of configClass Set<methodmetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces // Handle the default method of the interface, traverse the interface of this class, judge whether there is a non abstract method annotated with @ Bean, and add it to the beanMethod of configClass processInterfaces(configClass, sourceClass); // Process superclass, if any // Recursively process the parent class. The upper level method of the parent class will recursively process if (sourceClass.getMetadata().hasSuperClass()) { // Judge that the parent class is not null, is not in knownSuperclasses, and does not start with Java String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }(1) Handle inner classes
if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); } // Register member (nested) classes that happen to be configuration classes themselves. private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { Collection<SourceClass> memberClasses = sourceClass.getMemberClasses(); // Determine whether there is an internal class. If not, don't handle it directly if (!memberClasses.isEmpty()) { List<SourceClass> candidates = new ArrayList<>(memberClasses.size()); for (SourceClass memberClass : memberClasses) { // It's also easy to determine whether a Configuration class is a Configuration class. We analyzed it before to determine whether there is @ Configuration annotation, @ Import, @ ImportResource on the class // @Component, @ ComponentScan, and @ Bean annotation methods if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { // Add to candidates and sort candidates.add(memberClass); } } OrderComparator.sort(candidates); for (SourceClass candidate : candidates) { // Prevent introduction of A prevent introduction of A into B,B into A if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { // Put it into the stack and traverse to handle these configuration classes. It is also recursive. Call the previous doProcessConfigurationClass to handle this configuration class processConfigurationClass(candidate.asConfigClass(configClass)); } finally { this.importStack.pop(); } } } } }(2) Process @ PropertySource annotation
@SpringBootApplication @PropertySource({"demo.properties"}) public class Springboot2Application {
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { // I won't go in here. It is mainly to read the file specified by @ PropertySource annotation and encapsulate it into a property set and put it into the environment processPropertySource(propertySource); } }(3) Process @ ComponentScan annotation
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately // Let's analyze the parse method first Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } // If it is a configuration class, recursively handle it again if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } }
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); // Set a bean name generator. The default is to use org.springframework.beans.factory.support.BeanNameGenerator scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); // It's the default ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); // Understanding is a metadata parser scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } // Set the scan resource mode under * * / *. class scanner.setResourcePattern(componentScan.getString("resourcePattern")); // Add IncludeFilter and ExcludeFilter for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } // Set whether to lazy load boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } // Resolve the scanned package path and add it to basePackages Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { // Resolve the package of basePackageClasses and add it to basePackages basePackages.add(ClassUtils.getPackageName(clazz)); } // If it is empty, add the package of the class where the annotation is declared to basePackages if (basePackages.isEmpty()) { // Generally, our main configuration class does not declare the path of package scanning, so the package where the main configuration class is located will be added here basePackages.add(ClassUtils.getPackageName(declaringClass)); } // Add an ExcludeFilter to skip the class that declares the annotation scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // Traverse all package paths for (String basePackage : basePackages) { // Get all the qualified beandefinitions under the package, and then traverse the process, which will be analyzed as follows Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); // Generating beanName from beanNameGenerator String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // These two if judgment logics are relatively simple, that is, set some Lazy and DependsOn properties if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // Here is to check whether the bean definition has been defined before if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); // Add the bean definition to the collection and register it in the container beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // Scan the class file under the specified package path and its subpackages, and encapsulate it as a Resource object // classpath*:com/lwh/springboot/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { candidates.add(sbd); } } } } } } return candidates; }
// filter through several filters previously set protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; }(4) Process @ Import annotation
// Process any @Import annotations // getImports method is to recursively scan all the annotations on configClass, and put the value of @ Import annotation annotation into importCandidates, as shown in the following figure processImports(configClass, sourceClass, getImports(sourceClass), true);
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { // Traversal judgment type in turn // One of them is this type ,@Import (AutoConfigu rationImportSelector.class ) // This is the principle of auto configuration. Import these classes if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); // Instantiate and call methods of xxxAware and inject related properties ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); // It is of type DeferredImportSelector if (selector instanceof DeferredImportSelector) { // deferredImportSelectors = new ArrayList<>() // In this case, the two parameters will be encapsulated and added to the deferredImportSelectors for later processing // After adding to deferredImportSelectors, the specific processing is this.deferredImportSelectorHandler.process(); this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // If not, get the class name array imported by @ Import String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); // And then recursively processImports(configClass, currentSourceClass, importSourceClasses, false); } } // @Import(AutoC onfigurationPackages.Registrar.class ), the annotation above our main configuration class is this type // This is used to import the BeanDefinition under the package where the main configuration class is located and its subpackages else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); // Here we take these two parameters as key and put value into a map // this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // Treat it as a common class, judge whether it is a configuration class, and deal with it recursively processConfigurationClass(candidate.asConfigClass(configClass)); } } } finally { this.importStack.pop(); } } }(5) Process @ ImportSource annotation
// This is a common way of injecting XML in Spring @SpringBootApplication @ImportResource("test.xml") public class Springboot2Application {
// Process any @ImportResource annotations // You can use @ ImportResource annotation to specify xml file and import BeanDefinition AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { // namely test.xml String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) { // This is to put it into the map. This is to store it uniformly. In step 5, 4) import bean definition this.importedResources.put(importedResource, readerClass); } private final Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>();(6) How to handle @ Bean annotation
// Process individual @Bean methods // Get the Bean method in the current class Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { // This is also added to the set. See the following code. It is also processed in step 5, 3) configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();(7) Handle default methods
// For example, the main configuration class implements this interface public interface ConfigurationInterface { @Bean default Pig pig(){ return new Pig(); } }
// Process default methods on interfaces processInterfaces(configClass, sourceClass); // Register default methods on interfaces implemented by the configuration class. // This is also the recursive processing of its parent interface. Judge whether the default method in the parent interface is the @ Bean method private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { for (SourceClass ifc : sourceClass.getInterfaces()) { Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc); for (MethodMetadata methodMetadata : beanMethods) { if (!methodMetadata.isAbstract()) { // Also in step 5, 3) configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } } processInterfaces(configClass, ifc); } }(8) Recursively handle parent classes
// Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } }
As mentioned above, most of the methods in step 4 have been analyzed, but there is another important step that has not been analyzed. It is to import the automatic configuration classes such as xxxAutoConfiguration. The following analysis:
// When parsing @ Import annotation, our AutoConfigurationImportSelector is of type DeferredImportSelector, which means deferring Import // Why delay import? Because AutoConfigurationImportSelector imports some default components to the container. If the container already has this type // Therefore, it is intended to wait for the spring boot container to parse the user-defined beans first, and then import them only when it is found that they are not // Some default components if (selector instanceof DeferredImportSelector) { // deferredImportSelectors = new ArrayList<>() // In this case, the two parameters will be encapsulated and added to the deferredImportSelectors for later processing // After adding to deferredImportSelectors, the specific processing is this.deferredImportSelectorHandler.process(); this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); }
// Step 4 starts from here public void parse(Set<beandefinitionholder> configCandidates) { // Delete part of the code. configCandidates here is the main configuration class represented by springBootApplication for (BeanDefinitionHolder holder : configCandidates) { // Get BeanDefinition BeanDefinition bd = holder.getBeanDefinition(); // Our SpringBootApplication will come this way. Let's analyze this first parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // Import xxxAutoConfiguration to the container. Let's analyze it now this.deferredImportSelectorHandler.process(); }
// The above method will come here public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { // Let's first analyze the getImports method grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } }); } } public Iterable<group.entry> getImports() { // The deferredImports here go through grouping.add (deferred import) added for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); } public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { // Let's first analyze the getAutoConfigurationEntry method AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); // Add to this autoConfigurationEntries this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { // Next, traverse and put it into the map successively, map < string, annotation data > entries = new LinkedHashMap < > this.entries.putIfAbsent(importClassName, annotationMetadata); } }
// This is the method of AutoConfigurationImportSelector class protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { // Determine whether automatic configuration is supported if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // The attributes attribute is shown in the figure above, which is used to filter the auto configuration class AnnotationAttributes attributes = getAttributes(annotationMetadata); // This is the auto configuration class in the loading container List<string> configurations = getCandidateConfigurations(annotationMetadata, attributes); // To remove the repetition, put it in the set and then in the list configurations = removeDuplicates(configurations); // Remove what should be excluded Set<string> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); // Through filter filtering, the following analysis shows that there are only 22 after filtering configurations = filter(configurations, autoConfigurationMetadata); // Release an event. It seems that there is no key to it fireAutoConfigurationImportEvents(configurations, exclusions); // Encapsulate configurations as AutoConfigurationEntry to return return new AutoConfigurationEntry(configurations, exclusions); } protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { // Determine whether this attribute is configured. If not, it defaults to true, // String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; }
protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // getSpringFactoriesLoaderFactoryClass() returns ENA bleAutoConfiguration.class , this is to get all the field configuration classes in the container List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; }
protected List<autoconfigurationimportfilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
4. Step 5 Analysis
// The above analysis is step 4 of configuration class parsing parser.parse(candidates); // See Step 5 below. The configuration classes parsed in step 4 will be placed in the configurationClasses Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); this.reader.loadBeanDefinitions(configClasses);
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // Determine whether to skip if (trackedConditionEvaluator.shouldSkip(configClass)) { return; } // Inject the BeanDefinition represented by this configuration class into the container if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } // Traverse all Bean methods of this configuration class, inject the BeanDefinition to be introduced by these @ Bean annotated methods for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } // Processing the configuration file specified by @ ImportResource on the configuration class is the common XML configuration method in Spring // This side will inject beandefinition into the container by parsing the XML file. This method is not recommended in spring boot, so we won't go further loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // Process the importBeanDefinitionRegistrars obtained when D in step 4 parses the @ Import annotation, and call its registerBeanDefinitions // Method to inject BeanDefinition into the container, which was already mentioned in the @ Import method loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry)); }
4, Summary
At this point, the configuration class parsing of SpringBoot is basically finished, briefly review:
- The first part introduces some classes and annotations that will be used when SpringBoot parses configuration classes, and explains their use and principle.
- The second part introduces the SpringBoot startup process, and leads to the step in which SpringBoot resolves configuration classes.
- The third part introduces the eight steps of spring boot parsing configuration class.
More content, please pay attention to vivo Internet technology WeChat official account.
Note: please contact wechat: Labs2020 for reprint.