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

image-20240112174205835

打开这个文件:可以看到这个里面有一堆的自动配置类,可以包含初始化类,监听器,还有接下来的主角: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个类是不是都注入了?答案是:并没有,而是按需注入。那么怎么才叫按需注入呢?我们接下来分析。

image-20240112215951129

按需注入

这里我们在这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,首先是restTemplateBuilderConfigurerrestTemplateBuilder,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。

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,可以看到确实存在。

image-20240112225222489

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帮我们配置好了。

好了,本片文章就到这里了,有问题欢迎交流。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值