SpringBoot相对于SSM来说,主要的优点就是简化了配置,不再需要像SSM哪有写一堆的XML配置,这些XML配置在大项目上会成为一种累赘,使得后期项目难以维护。
SpringBoot的出现,使得开发者不再关注于配置,能够更加专注于业务的开发,这得益于SpringBoot的自动配置。
SpringBoot的自动配置的核心就在于SpringBoot启动类中的@SpringBootApplication
注解上
@SpringBootApplication
@Slf4j
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
log.info("Project running .....");
}
}
这是一个复合注解,标识该类为SpringBoot的应用入口,里面包含了SpringBootConfiguration
、EnableAutoConfiguration
、ComponentScan
三个注解
@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 {
....
}
三个注解的作用如下
SpringBootConfiguration
:这是SpringBoot框架中的一个特殊注解,属于@Configuration
派生注解,它的作用是在应用启动时会被自动加载和处理,简化应用程序的配置过程,提供快速的启动配置.EnableAutoConfiguration
:启用自动配置机制(自动配置的核心)ComponentScan
:扫描启动类路径下的类,自动注册带有@Component以及其他相关注解的类到Spring容器中EnableAutoConfiguration
翻译过来就是开启自动配置,说明这个类的作用就是开启自动配置的作用。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
EnableAutoConfiguration
也是一个复合注解,其中AutoConfigurationPackage
注解的作用是将当前类所在的包以及子包作为自动配置的包路径,以便让SpringBoot能够自动加载和处理这些组件的配置。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
AutoConfigurationPackage
中有@Import({AutoConfigurationPackages.Registrar.class})
@Import({AutoConfigurationPackages.Registrar.class})
的作用是导入一个配置文件,在springboot中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class的内部类Registrar.class
执行逻辑来决定是如何导入的。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
Registrar
作为一个静态内部类,实现了ImportBeanDefinitionRegistrar
接口,就可以被@Import
注入到Spring容器中。
在这里类中,需要重点关注registerBeanDefinitions
方法
通过DEBUG发现,(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])
的值为当前启动类所在的包名。
因此,可以得出结论,这个方法的主要作用是启动类所在的包下的所有组件注入到Spring容器中。
接着,再看@Import({AutoConfigurationImportSelector.class})
通过@Import
将AutoConfigurationImportSelector
选择器导入。
在AutoConfigurationImportSelector
需要重点关注getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
// 检查是否开始自动配置,如果返回false,则不开启,不需要导入任何配置类
return EMPTY_ENTRY;
} else {
//从注解元数据中获取自动配置相关属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//根据注解元数据和属性,获取潜在的候选自动配置类的列表
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去除重复的自动配置类
configurations = this.removeDuplicates(configurations);
//获取与自动配置相关的排除列表,即需要从候选自动配置中排除的类。
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//检查排除列表中的类是否存在于候选自动配置列表中,并进行必要的处理。
this.checkExcludedClasses(configurations, exclusions);
//从候选自动配置列表中移除在排除列表中指定的类。
configurations.removeAll(exclusions);
//使用配置类过滤器(ConfigurationClassFilter)对候选自动配置列表进行进一步筛选和过滤。
configurations = this.getConfigurationClassFilter().filter(configurations);
//触发自动配置导入事件,可以通知其他监听器关于自动配置的导入信息。
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
这个方法的返回值是候选的配置类,经过处理和筛选后的自动配置类列表以及排除列表。
重点是关注如何获取自动配置类列表,这个需要关注getCandidateConfigurations
方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//使用 SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载所有的工厂名称,并将它们存储在一个新的 ArrayList 中。
//getSpringFactoriesLoaderFactoryClass() 返回用于加载自动配置的工厂类。
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
//使用 ImportCandidates 从指定的类路径加载 AutoConfiguration 类的子类或实现类,并将它们添加到候选配置列表中。
//this.getBeanClassLoader() 返回当前线程的类加载器。
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
这个方法的主要作用就是利用SpringFactoriesLoader
加载META-INF/spring.factories
文件
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
//配置类加载器
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
接着查看loadSpringFactories
,这里面就是获取需要配置的类的主要核心过程了。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//首先从缓存中获取给定类加载器(classLoader)对应的工厂配置信息。如果缓存中已存在,则直接返回结果。
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
//用于存储工厂配置信息。
Map<String, List<String>> result = new HashMap();
try {
//使用给定的类加载器获取所有位于 META-INF/spring.factories 路径下的资源文件的 URL 枚举
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
//遍历每个资源文件的 URL
while(urls.hasMoreElements()) {
//获取下一个资源文件的 URL
URL url = (URL)urls.nextElement();
//将 URL 封装为 UrlResource 对象,以便进行读取。
UrlResource resource = new UrlResource(url);
//通过 PropertiesLoaderUtils 加载 UrlResource 对象所代表的资源文件,并将其作为属性对象 Properties 进行读取。
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
//遍历每个属性条目。
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
//提取工厂类型名称,并进行去除首尾空格的处理。
String factoryTypeName = ((String)entry.getKey()).trim();
//将工厂实现类名按逗号分隔,转换为字符串数组。
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
//将工厂实现类名添加到对应的工厂类型键下的列表中。如果该键不存在,则创建一个新的空列表。
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
//对结果中的每个工厂类型及其对应的实现类列表,进行去重操作,并将列表转换为不可修改的集合,以确保唯一性
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
//将结果存储到缓存中,以便下次直接获取
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
也就是说这个方法通过加载和解析 META-INF/spring.factories
文件中的内容,将工厂类型和其对应的实现类名关联起来,并返回一个包含工厂配置信息的 Map
对象。它使用了缓存机制来避免重复加载相同的配置文件,提高了性能。这些工厂配置信息在 Spring Boot 中用于自动装配和初始化各种组件、功能和设置。
这个
META-INF/spring.factories
文件指什么呢?在我们导入的每一个
XXX-spring-boot-starter
中,除了本身的jar包以外,还会有一个xxx-spring-boot-autoConfigure
,这个就是第三方依赖自己编写的自动配置类。我们现在就以 spring-boot-autocongigure 这个依赖来说。这些类就是自动配置的类了。就
RedisAutoConfiguration
而言@AutoConfiguration @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public 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); } }
里面已经配置好了,Redis参数指定了
RedisProperties
配置文件,还有数据源配置文件。我们只需要在yml文件配置好,后面的全交给SpringBoot框架自己配置即可。
@Conditional
其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
@ConditionalOnClass
: classpath中存在该类时起效@ConditionalOnMissingClass
: classpath中不存在该类时起效@ConditionalOnBean
: DI容器中存在该类型Bean时起效@ConditionalOnMissingBean
: DI容器中不存在该类型Bean时起效@ConditionalOnSingleCandidate
: DI容器中该类型Bean只有一个或@Primary的只有一个时起效@ConditionalOnExpression
: SpEL表达式结果为true时@ConditionalOnProperty
: 参数设置或者值一致时起效@ConditionalOnResource
: 指定的文件存在时起效@ConditionalOnJndi
: 指定的JNDI存在时起效@ConditionalOnJava
: 指定的Java版本存在时起效@ConditionalOnWebApplication
: Web应用环境下起效@ConditionalOnNotWebApplication
: 非Web应用环境下起效参考:
- 一文搞懂SpringBoot自动配置原理 - 掘金 (juejin.cn)
- SpringBoot自动配置原理详解 - 掘金 (juejin.cn)