spring官网:Spring | Home
maven管理:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
从以上来看,我们已经把一个springboot项目构建完毕了,那么我们的依赖里面并没有指定具体的version,那这个是怎么引入进来的呢,x现在我们就来看看parent里面做了那些操作
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.4</version>
</parent>
可以看出parent还有一个spring-boot-dependencies的封装
可以看出以上已经对依赖管理做好的版本控制,具体版本太多我就不一一列出来了
所以说SpringBoot可以理解为封装再封装,将封装做到极致来简化开发
我们已经完成了一个SpringBoot项目,可以看出确实比之前SpringFramework简洁了,当然这不是我随意写的都是以官网方式为准的:Spring | Spring Quickstart Guide
SpringBoot自动配置
server:
port: 8888
以上我们只对启动端口进行了修改,那么springBoot是如何自动加载的呢
@ConfigurationProperties(
prefix = "server",
ignoreUnknownFields = true
)
public class ServerProperties
跟进源码可以看出,server前缀的配置文件都是由这个类来维护的,这么说那我们是不是也可以自定义配置
@ConfigurationProperties(prefix = "studentyaml")
@ToString
@Data
public class StudentYaml {
private String name;
private Boolean sex;
private Date birth;
private Integer age;
private Address address;
private String[] interests;
private List<String> fridends;
private Map<String, Double> score;
private Set<Double> weightHis;
private Map<String, List<Address>> allFridendsAddress;
}
studentyaml:
name: muse
sex: true
birth: 2021/9/5
age: 1
street: 王府井⼤街
interests: [看书, 写字, 玩电⼦游戏]
fridends:
- 李雷
- 韩梅梅
score: {math: 100,english: 80}
weight-his:
- 176
- 199
- 160
- 179
以上我们也基于ServerProperties自定义了一个StudentYaml,那么我们来看看,springBoot启动的时候是否会自动加载我们自定义的配置呢
ConfigurableApplicationContext可以理解整个SpringBoot的上下文对象(全局变量类型Mybatis中的Configuration)
可以看出我们自定义的配置并没有自动给配置进去,那么我们可以看看ServerProperties还有那么操作我们是不是遗漏了
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration
@EnableConfigurationProperties({ServerProperties.class})这行才是我们实际关注的,那我们也来加上试试
可以看出我们已经实现了SpringBoot的自定义配置
总结以上可以看出,
@EnableConfigurationProperties({StudentYaml.class})这个注解才是SpringBoot强大的自动配置功能,下面我们再来跟进源码看看基本的实现方式
自动配置原理
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
由@SpringbootApplication可以看出核心注解@EnableAutoConfiguration(开启自动配置)
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration
@EnableAutoConfiguration核心@AutoConfigurationPackage(自动配置包)@Import(导入注解)
我们先来看@AutoConfigurationPackage
@Import({Registrar.class})
public @interface AutoConfigurationPackage
核心主要也是将Registar引入
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
以上代码就是Registar核心,显然已经看不懂了,那么我们可以跟进dubug看下
可以看出这步主要是为了设置DemoApplication的根目录以及子级目录,所以说我们要把DemoApplication放在项目的最高级中。
@Import({AutoConfigurationImportSelector.class})这又有哪些操作呢(导入所有符合自动注入的bean注入到ioc)
以上可以看出
AutoConfigurationImportSelector只是实现了DeferImportSelector,并不是直接实现了ImportSelecor,所以spring会先调用getImportGroup,拿到Group类之后,在调用process,再调用selectImports获取需要引入配置集合,然后交给spring解析处理,所以AutoConfigurationImportSelector导入的时候,不会直接调用
public String[] selectImports(AnnotationMetadata annotationMetadata)
因此我们来看看process和selectImports做了那些操作
process:
由debug可以看出,这里主要是为了拿出配置类和排除类,然后维护到本地
autoConfigurationEntries中,为下一步selectImports准备,那么我们来看看具体是怎么获取的配置类
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 获取标注@EnableAutoConfiguration的元数据。
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);
configurations = this.getConfigurationClassFilter().filter(configurations);
//监听器记录配置类和排除类
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 数据组装
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
以上只是为了拿到配置类和排除类做处理,以下才是获取配置类核心代码:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (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);
}
}
}
第一次url:file:/E:/resporitory/org/springframework/boot/spring-boot/2.5.4/spring-boot-2.5.4.jar!/META-INF/spring.factories
第二次url:file:/E:/resporitory/org/springframework/spring-beans/5.3.9/spring-beans-5.3.9.jar!/META-INF/spring.factories
第三次url:file:/E:/resporitory/org/springframework/boot/spring-boot-autoconfigure/2.5.4/spring-boot-autoconfigure-2.5.4.jar!/META-INF/spring.factories
从debug可以看出,目前url只会有三次
将以上文件中的数据去重之后以hashmap的方式存储到本地缓存
selectImports:
以上说到process可以获取所有的配置类和排除类,那再来看看selectImports又做了那些操作
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
} else {
// 取出排除配置
Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
// 取出需要的配置
Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
// 去除需要排除的配置并封装数据返回
processedConfigurations.removeAll(allExclusions);
return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
}).collect(Collectors.toList());
}
}
看完以上代码之后,个人感觉这步操作毫无任何意义,只是再做了一次取出配置操作
总结以上代码
@Import({AutoConfigurationImportSelector.class})这段主要是引入了AutoConfigurationGroup中的autoConfigurationEntries,因此我们在配置文件中可以自动加载到这些已经自动配置好的类
以上就是个人所理解的自动配置的原理
SpringBoot自动配置特征
1.按需加载原则
看完以上代码,我们就能感觉到每次我们启动SpringBoot项目的时候,是不是每次都要加载那么自动配置的类,那么问题来了,如果我们有些自动配置的类都不需要,为啥还需要加载那么多无关紧要的呢,我们再来看看SpringBoot是怎么解决这个问题呢
@Configuration(
proxyBeanMethods = false
)
// 只有在引入RabbitTemplate.class和Channel.class
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration
以上代码就是主要我们以上按需加载的问题
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
2.容错兼容
@Bean
@ConditionalOnBean(MultipartResolver.class) // 作⽤于该⽅法上,要求我们的IOC中存在
MultipartResolver类型的bean
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //
//要求IOC容器中不存在名称为multipartResolver的bean
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 调⽤者IOC,resolver就是从IOC中获取到的类型为MultipartResolver的bean(是
MultipartResolver类型的但是名称不是“multipartResolver”)
return resolver; // 保存到IOC中的bean的名字就是“multipartResolver”
}
这样可以保证注入到IOC容器的MultipartResolver的名字就是multipartResolver
3.用户配置优先
@Bean(
name = {"multipartResolver"}
)
// 在IOC中不存在MultipartResolver类的时候,才会自动配置
@ConditionalOnMissingBean({MultipartResolver.class})
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
以上是为了解决,如果我们已经自定义配置了,那么已用户为准,不然我就自动配置了