2024源码开篇--Springboot自动装配原理,看完不会来打我!
在Springboot的自动装配令开发人员减轻了大量的配置负担。
那么什么是自动装配?
自动装配就是在Spring启动过程中,装载了必要的Bean,以方便在开发的时候调用,而不用开发人员自己配置。
下面是一个Spring的启动类:
1 2 3 4 5 6 7 | @SpringBootApplication public class LasSystemApplication { public static void main(String[] args) { SpringApplication.run(LasSystemApplication. class , args); } } |
自动装配主要是通过注解@SpringBootApplication
实现的,具体是怎么实现的,请继续阅读。
源码&&原理
注解分析
SPringBootApplication注解分为下面几个注解,其中和自动装配的核心注解为EnableAutoConfiguration
,之后着重分析这个注解。
1 2 3 4 5 6 7 8 9 10 11 | @Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan (excludeFilters = { @Filter (type = FilterType.CUSTOM, classes = TypeExcludeFilter. class ), @Filter (type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter. class ) }) public @interface SpringBootApplication { //省略 } |
EnableAutoConfiguration
注解分析:
除了元注解,主要的注解为两个:一个是@AutoConfigurationPackage
,另一个是@Import(AutoConfigurationImportSelector.class)
,下面一个一个进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import (AutoConfigurationImportSelector. class ) public @interface EnableAutoConfiguration { /** * Environment property that can be used to override when auto-configuration is * enabled. */ String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; } |
AutoConfigurationPackage
相似的,除了元注解,只剩下@Import(AutoConfigurationPackages.Registrar.class)
这个注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @Import (AutoConfigurationPackages.Registrar. class ) public @interface AutoConfigurationPackage { /** * Base packages that should be registered with {@link AutoConfigurationPackages}. * <p> * Use {@link #basePackageClasses} for a type-safe alternative to String-based package * names. * @return the back package names * @since 2.3.0 */ String[] basePackages() default {}; /** * Type-safe alternative to {@link #basePackages} for specifying the packages to be * registered with {@link AutoConfigurationPackages}. * <p> * Consider creating a special no-op marker class or interface in each package that * serves no purpose other than being referenced by this attribute. * @return the base package classes * @since 2.3.0 */ Class<?>[] basePackageClasses() default {}; } |
继续分析看到导入了一个类:AutoConfigurationPackages.Registrar.class
,这个类中有一个核心方法:registerBeanDefinitions
,这个方法主要是加载用户自己包下的Bean。(在启动的时候可以自行Debug)
自动装配的Bean分为两部分:
一个是用户自己定义的Bean,另一个是用户在pom文件中引入的依赖Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing * configuration. */ static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //启动的时候这里的metadata就是主启动类的全类名,会扫描与主启动类同级的包、 //将这些包下的Bean注册到Bean容器中 register(registry, new PackageImports(metadata).getPackageNames().toArray( new String[ 0 ])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton( new PackageImports(metadata)); } } |
AutoConfigurationImportSelector
这个类中有一个核心方法,getAutoConfigurationEntry
,这个方法将依赖的Bean加载到Bean容器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } |
这个方法里面主要逻辑:
通过这个方法去获取所有的候选配置,这个方法通过SpringFactoriesLoader去加载所有的META-INF/spring.factories
里面的配置。
1 2 3 4 5 6 7 8 | List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct." ); return configurations; } |
主要分析SpringFactoriesLoader.loadFactoryNames
这个方法,这个方法里面的有一个方法loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
而其下面又有一个主要方法classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
这个方法就可以解释为什么加载所有的META-INF/spring.factories
里面的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null ) { classLoaderToUse = SpringFactoriesLoader. class .getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = cache.get(classLoader); if (result != null ) { return result; } result = new HashMap<>(); try { Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException( "Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]" , ex); } return result; } |
继续深入,可以看到这个方法是去找到所有的给定名字的资源,而这个地方我们给定的名字是META-INF/spring.factories
,所有这个方法就是把你的项目依赖里面所有的资源文件查出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public Enumeration<URL> getResources(String name) throws IOException { Objects.requireNonNull(name); @SuppressWarnings ( "unchecked" ) Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[ 2 ]; if (parent != null ) { tmp[ 0 ] = parent.getResources(name); } else { tmp[ 0 ] = BootLoader.findResources(name); } tmp[ 1 ] = findResources(name); return new CompoundEnumeration<>(tmp); } |
spring-boot-autoconfigure
为什么单独拎出来这个依赖,因为我们平时所使用的大部分依赖都要使用到这个自动配置依赖,为什么这么说呢,我们继续分析。
打开源码结构,可以看到autoconfigure中有想要找的文件,spring.factories
打开这个文件:可以看到这个里面有一堆的自动配置类,可以包含初始化类,监听器,还有接下来的主角:EnableAutoConfiguration等等。那么这么多的类是不是都要加载,答案是不是的,至于为什么,请往下阅读。
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ ......太多了,省略了
加载EnableAutoConfiguration
在上面提到的方法getCandidateConfigurations
中有一个函数,getSpringFactoriesLoaderFactoryClass()
这个方法很简单,返回的就是EnableAutoConfiguration.class;
这个类(下方第二个函数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct." ); return configurations; } /** * Return the class used by {@link SpringFactoriesLoader} to load configuration * candidates. * @return the factory class */ protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration. class ; } |
然后我们在继续看loadFactoryNames方法,这里为了好理解,我做了一个Debug,可以看到默认会拿到key为EnableAutoConfiguration的配置类,这里有133个,这133个就是spring.factories里面org.springframework.boot.autoconfigure.EnableAutoConfiguratio下面的配置类。到此。我们知道了在哪找,以及找哪些类了,下面我们分析这133个类是不是都注入了?答案是:并没有,而是按需注入。那么怎么才叫按需注入呢?我们接下来分析。
按需注入
这里我们在这133个待注入Bean中,随便分析几个,其他的都是一样的分析方法。
1.org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
这里分析这个配置类,可以看到是RestTemplate的自动配置类,首先关注这个类上的注解:
1 2 3 4 5 6 7 | @Configuration (proxyBeanMethods = false ) //告诉Spring这是一个配置类 @AutoConfigureAfter (HttpMessageConvertersAutoConfiguration. class ) //在HttpMessageConvertersAutoConfiguration之后装配 @ConditionalOnClass (RestTemplate. class ) //项目红是否有RestTemplate.class这个类,如果有才装配 @Conditional (NotReactiveWebApplicationCondition. class ) //是非响应式Web应用 public class RestTemplateAutoConfiguration { //..... } |
通过这几个注解我们看到这几个条件都是满足的,然后看这个配置类往容器中注入了哪些Bean,首先是restTemplateBuilderConfigurer
,restTemplateBuilder
,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public class RestTemplateAutoConfiguration { @Bean @Lazy //懒加载 @ConditionalOnMissingBean //表示没有这个Bean才注入 public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer( ObjectProvider<HttpMessageConverters> messageConverters, ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers, ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) { RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer(); configurer.setHttpMessageConverters(messageConverters.getIfUnique()); configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList())); configurer.setRestTemplateRequestCustomizers( restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList())); return configurer; } @Bean @Lazy @ConditionalOnMissingBean public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) { RestTemplateBuilder builder = new RestTemplateBuilder(); return restTemplateBuilderConfigurer.configure(builder); } static class NotReactiveWebApplicationCondition extends NoneNestedConditions { NotReactiveWebApplicationCondition() { super (ConfigurationPhase.PARSE_CONFIGURATION); } @ConditionalOnWebApplication (type = Type.REACTIVE) private static class ReactiveWebApplication { } } } |
在Bean容器中获取这两个Bean,可以看到确实存在。
2.org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
在分析一个自动配置类,可以看出这是Redis的自动配置类,当满足条件时,这个类会把redisTemplate和stringRedisTemplate放入到容器中,显然他是不满足这个条件的,因为目前的项目中并没有RedisOperations这个类,所以下面的两个bean也就没必要看了,肯定不会注入的,下面再验证一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration (proxyBeanMethods = false ) @ConditionalOnClass (RedisOperations. class ) @EnableConfigurationProperties (RedisProperties. class ) @Import ({ LettuceConnectionConfiguration. class , JedisConnectionConfiguration. class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean (name = "redisTemplate" ) @ConditionalOnSingleCandidate (RedisConnectionFactory. class ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate (RedisConnectionFactory. class ) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return new StringRedisTemplate(redisConnectionFactory); } } |
验证程序:
1 2 3 4 5 6 7 8 9 10 | public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(InitProjectTemplateApplication. class , args); boolean beanDefinition = run.containsBeanDefinition( "redisTemplate" ); System.out.println( "redisTemplate是否存在-->" +beanDefinition); boolean builder = run.containsBeanDefinition( "stringRedisTemplate" ); System.out.println( "stringRedisTemplate是否存在-->" +builder); } 输出: redisTemplate是否存在--> false stringRedisTemplate是否存在--> false |
什么时候这两个bean才会被放进去,答案是必须要有这个类import org.springframework.data.redis.core.RedisOperations,这个类就是redis的starter里面的,所以我们平时开发的时候,引入一个spring-boot-redis-starter就可以用redisTemplate或者stringRedisTemplate直接操作redis了,一切都是spring帮我们配置好了。
好了,本片文章就到这里了,有问题欢迎交流。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)