2024源码开篇-Springboot自动装配原理!

前言

在Springboot的自动装配令开发人员减轻了大量的配置负担。

那么什么是自动装配?

自动装配就是在Spring启动过程中,装载了必要的Bean,以方便在开发的时候调用,而不用开发人员自己配置。

下面是一个Spring的启动类:

typescript 复制代码
@SpringBootApplication
public class LasSystemApplication {
    public static void main(String[] args) {
        SpringApplication.run(LasSystemApplication.class, args);
    }
​
}

自动装配主要是通过注解@SpringBootApplication实现的,具体是怎么实现的,请继续阅读。

源码&&原理

注解分析

SPringBootApplication注解分为下面几个注解,其中和自动装配的核心注解为EnableAutoConfiguration,之后着重分析这个注解。

less 复制代码
@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),下面一个一个进行分析。

less 复制代码
@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)这个注解。

less 复制代码
@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。

typescript 复制代码
    /**
     * {@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容器中。

scss 复制代码
    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里面的配置。

ini 复制代码
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
typescript 复制代码
    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里面的配置。

ini 复制代码
	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,所有这个方法就是把你的项目依赖里面所有的资源文件查出来。

scss 复制代码
    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等等。那么这么多的类是不是都要加载,答案是不是的,至于为什么,请往下阅读。

ini 复制代码
# 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;这个类(下方第二个函数)。

typescript 复制代码
	/**
	 * 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的自动配置类,首先关注这个类上的注解:

less 复制代码
@Configuration(proxyBeanMethods = false)//告诉Spring这是一个配置类
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)//在HttpMessageConvertersAutoConfiguration之后装配
@ConditionalOnClass(RestTemplate.class)//项目红是否有RestTemplate.class这个类,如果有才装配
@Conditional(NotReactiveWebApplicationCondition.class)//是非响应式Web应用
public class RestTemplateAutoConfiguration {
	//.....
}

通过这几个注解我们看到这几个条件都是满足的,然后看这个配置类往容器中注入了哪些Bean,首先是restTemplateBuilderConfigurerrestTemplateBuilder,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。

less 复制代码
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也就没必要看了,肯定不会注入的,下面再验证一下。

kotlin 复制代码
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);
	}

}

验证程序:

arduino 复制代码
    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帮我们配置好了。

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

之后会继续发布更多的文章!

相关推荐
小马爱打代码11 分钟前
SpringBoot原生实现分布式MapReduce计算
spring boot·分布式·mapreduce
iuyou️14 分钟前
Spring Boot知识点详解
java·spring boot·后端
一弓虽26 分钟前
SpringBoot 学习
java·spring boot·后端·学习
来自星星的猫教授2 小时前
spring,spring boot, spring cloud三者区别
spring boot·spring·spring cloud
乌夷3 小时前
使用spring boot vue 上传mp4转码为dash并播放
vue.js·spring boot·dash
A阳俊yi5 小时前
Spring Boot日志配置
java·spring boot·后端
苹果酱05675 小时前
2020-06-23 暑期学习日更计划(机器学习入门之路(资源汇总)+概率论)
java·vue.js·spring boot·mysql·课程设计
斜月5 小时前
一个服务预约系统该如何设计?
spring boot·后端
Java水解6 小时前
线程池详解:在SpringBoot中的最佳实践
spring boot·后端
阿里小阿希7 小时前
解决 Spring Boot + MyBatis 项目迁移到 PostgreSQL 后的数据类型不匹配问题
spring boot·postgresql·mybatis