前言
在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,首先是restTemplateBuilderConfigurer
,restTemplateBuilder
,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。
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帮我们配置好了。
好了,本片文章就到这里了,有问题欢迎交流。
之后会继续发布更多的文章!