2024源码开篇--Springboot自动装配原理,看完不会来打我!
在Springboot的自动装配令开发人员减轻了大量的配置负担。
那么什么是自动装配?
自动装配就是在Spring启动过程中,装载了必要的Bean,以方便在开发的时候调用,而不用开发人员自己配置。
下面是一个Spring的启动类:
@SpringBootApplication public class LasSystemApplication { public static void main(String[] args) { SpringApplication.run(LasSystemApplication.class, args); } }
自动装配主要是通过注解@SpringBootApplication
实现的,具体是怎么实现的,请继续阅读。
源码&&原理
注解分析
SPringBootApplication注解分为下面几个注解,其中和自动装配的核心注解为EnableAutoConfiguration
,之后着重分析这个注解。
@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)
,下面一个一个进行分析。
@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)
这个注解。
@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。
/** * {@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容器中。
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
里面的配置。
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
里面的配置。
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
,所有这个方法就是把你的项目依赖里面所有的资源文件查出来。
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;
这个类(下方第二个函数)。
/** * 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的自动配置类,首先关注这个类上的注解:
@Configuration(proxyBeanMethods = false)//告诉Spring这是一个配置类 @AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)//在HttpMessageConvertersAutoConfiguration之后装配 @ConditionalOnClass(RestTemplate.class)//项目红是否有RestTemplate.class这个类,如果有才装配 @Conditional(NotReactiveWebApplicationCondition.class)//是非响应式Web应用 public class RestTemplateAutoConfiguration { //..... }
通过这几个注解我们看到这几个条件都是满足的,然后看这个配置类往容器中注入了哪些Bean,首先是restTemplateBuilderConfigurer
,restTemplateBuilder
,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。
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也就没必要看了,肯定不会注入的,下面再验证一下。
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); } }
验证程序:
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帮我们配置好了。
好了,本片文章就到这里了,有问题欢迎交流。