When doing back-end development, the first contact is the basic spring. In order to provide bean s by referring to two-party packages, we need to add the corresponding package < context: component-scan base-package= "xxxx" /> or add the annotation @ComponentScan ({"xxx"}). At that time, I thought it was urgly, but I did not study whether there was a better way.
Until contact with Spring Boot, it was found that it could automatically introduce two-way packaged bean s. Nevertheless, I haven't looked at the implementation principle of this block. Not until recently was I asked about it in an interview. So look at the implementation logic.
Posture
Before explaining the principle, use the posture first.
Define a bean in project A.
package com.wangzhi; import org.springframework.stereotype.Service; @Service public class Dog { }
And create a file called spring.factories under resources/META-INF / of the project, which reads as follows
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
Then refer to the jar package of project A in project B.
The project A code is as follows:
package com.wangzhi.springbootdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; @EnableAutoConfiguration public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(context.getBean(com.wangzhi.Dog.class)); } }
Print results:
com.wangzhi.Dog@3148f668
Principle analysis
Overall, there are two parts: one is to collect all the classes of Enable AutoConfiguration related bean s in spring.factories, and the other is to register the obtained classes into spring containers.
Collecting bean definition classes
When the spring container starts, it calls AutoConfiguration Import Selector # getAutoConfiguration Entry
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // EnableAutoConfiguration Attributes of annotations: exclude,excludeName etc. AnnotationAttributes attributes = getAttributes(annotationMetadata); // Get all Configurations List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // Duplicate removal configurations = removeDuplicates(configurations); // Delete exclude Classes specified in Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } getCandidateConfigurations Calls to methods loadFactoryNames: public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // factoryClassName by org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryClassName = factoryClass.getName(); // This method returns all spring.factories Documentation key by org.springframework.boot.autoconfigure.EnableAutoConfiguration Class path of return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // Find all"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); // Read the contents of the file. properties Be similar to HashMap,Containing attributes key and value Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); // Property files can be used','Divide multiple value for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
Register to Container
In the above process, we get the class paths of all bean s specified in spring.factories, which are processed in the processGroupImports method. @import The same logic as the annotation imports it into the container.
public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { // getImports That is, the encapsulation of all Classpaths obtained above grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { // And processing@Import The same notes processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { ... // Traversing the collected classpath for (SourceClass candidate : importCandidates) { ... //If candidate yes ImportSelector or ImportBeanDefinitionRegistrar Types will have different processing logic, not to mention here // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // Regard as @Configuration Handle processConfigurationClass(candidate.asConfigClass(configClass)); ... } ... }
As you can see, the bean class definition collected in the first step will eventually be registered in the container in the same way as the Configuration process.
End
@ The Enable AutoConfiguration annotation simplifies the cost of importing a two-party package bean. To provide a two-way package for other applications, you just need to define the exposed beans in the two-way package in spring.factories. For unnecessary beans, exclude can be done using the exclude attribute of @EnableAutoConfiguration.
Portal: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q