虽然我们在日常开发中,Spring Boot 使用非常多,算是目前 Java 开发领域一个标配了,但是小伙伴们仔细想想自己的面试经历,和 Spring Boot 相关的面试题都有哪些?个人感觉应该是比较少的,Spring Boot 本质上还是曾经 SSM 那一套,只是通过各种 starter 简化了配置而已,其他都是一模一样的,所以 Spring Boot 中很多面试题还是得回归到 Spring 中去解答!当然这并不是说 Spring Boot 中没什么可问的,Spring Boot 中其实也有一个非常经典的面试题,那就是 Spring Boot 中的自动化配置是怎么实现的?今天松哥就来和各位小伙伴聊一下这个问题。
为梅州等地区用户提供了全套网页设计制作服务,及梅州网站建设行业解决方案。主营业务为网站建设、成都网站建设、梅州网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
其实松哥之前和小伙伴们聊过相关的问题,不过都是零散的,没有系统梳理过,之前也带领小伙伴们自定义过一个 starter,相信各位小伙伴对于 starter 的原理也有一定了解,所以今天这篇文章一些过于细节的内容我就不赘述了,大家可以翻看之前的文章。
要说 Spring Boot 的自动化配置,那必须从项目的启动类 @SpringBootApplication 说起,这是整个 Spring Boot 宇宙的起点,我们先来看下这个注解:
@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 {
}
可以看到,@SpringBootApplication 注解组合了多个常见注解的功能,其中:
别看这里注解多,其实真正由 Spring Boot 提供的注解一共就两个,分别是 @SpringBootConfiguration 和 @EnableAutoConfiguration 两个,其他注解在 Spring Boot 出现之前就已经存在多年了。
接下来我们来看看 @EnableAutoConfiguration 是如何实现自动化配置的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
这个注解起关键作用的就是两个东西:
AutoConfigurationImportSelector 类中的方法比较多,入口的地方则是 process 方法,所以我们这里就从 process 方法开始看起:
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
从类名就可以看出来,跟自动化配置相关的对象是由 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); 进行加载的。
当然这里的 getAutoConfigurationEntry 方法实际上就是当前类提供的方法,我们来看下该方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
这里源码的方法命名都做的不错,基本上都能做到见名知意,小伙伴们日常开发中,应该向这样的命名思路看齐。接下来我们就来挨个看一下这里的关键方法。
首先调用 isEnabled 方法去判断自动化配置到底有没有开启,这个主要是因为我们及时在项目中引入了 spring-boot-starter-xxx 之后,我们也可以通过在 application.properties 中配置 spring.boot.enableautoconfiguration=false 来关闭所有的自动化配置。
相关源码如下:
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
接下来调用 getCandidateConfigurations 方法去获取所有候选的自动化配置类,这些候选的自动化配置类主要来自两个地方:
相关源码如下:
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, 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;
}
这里加载到的自动化配置类的全路径被存入到 configurations 对象中,该对象有两个获取的地方:
如果这两个地方都没有加载到任何自动化配置类,那么就会抛出一个异常。
removeDuplicates 方法表示移除候选自动化配置类中重复的类,移除的思路也很有意思,就用一个 LinkedHashSet 中转一下就行了,源码如下:
protected final List removeDuplicates(List list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
可以看到这些源码里有时候一些解决思路也很有意思。
getExclusions 方法表示需要获取到所有被排除的自动化配置类,这些被排除的自动化配置类可以从三个地方获取:
来看一下相关源码:
protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
跟上面讲解的三点刚好对应。
这个方法是检查所有被排除的自动化配置类,由于 Spring Boot 中的自动化配置类可以自定义,并不需要统一实现某一个接口或者统一继承某一个类,所以在写排除类的时候,如果写错了编译是校验不出来的,像下面这种:
@SpringBootApplication(exclude = HelloController.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
由于 HelloController 并不是一个自动化配置类,所以这样写项目启动的时候就会报错,如下:
这个异常从哪来的呢?其实就是来自 checkExcludedClasses 方法,我们来看下该方法:
private void checkExcludedClasses(List configurations, Set exclusions) {
List invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}
可以看到,在 checkExcludedClasses 方法中,会首先找到所有位于当前类路径下但是却不包含在 configurations 中的所有被排除的自动化配置类,由于 configurations 中的就是所有的自动化配置类了,所以这些不存在于 configurations 中的类都是有问题的,都不是自动化配置类,将这些有问题的类收集起来,存入到 invalidExcludes 变量中,然后再进行额外的处理。
所谓额外的处理就是在 handleInvalidExcludes 方法中抛出异常,前面截图中的异常就是来自这里。
这个方法就一个任务,就是从 configurations 中移除掉那些被排除的自动化配置类。configurations 本身就是 List 集合,exclusions 则是一个 Set 集合,所以这里直接移除即可。
现在我们已经加载了所有的自动化配置类了,但是这些配置类并不是都会生效,具体是否生效,还要看你的项目是否使用了具体的依赖。
例如,现在加载的自动化配置里里边就包含了 RedisAutoConfiguration,这个是自动配置 Redis 的,但是由于我的项目中并没有使用 Redis,所以这个自动化配置类并不会生效。这个过程就是由 getConfigurationClassFilter().filter(configurations); 来完成的。
先说一个预备知识:
由于我们项目中的自动化配置类特别多,每一个自动化配置类都会依赖别的类,当别的类存在时,这个自动化配置类才会生效,这一堆互相之间的依赖关系,存在于 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 文件之中,我随便举一个该文件中的配置:
我们来看看 RabbitAnnotationDrivenConfiguration 类的注解:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
}
这个类和配置文件中的内容一致。
这个预备知识搞懂了,接下来的内容就好理解了。
先来看 getConfigurationClassFilter 方法,这个就是获取所有的过滤器,如下:
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
可以看到,这里获取到的过滤器都是 AutoConfigurationImportFilter 类型的,这个类型的过滤器只有三个实例,如下图:
从这三个实例的名字中,基本上就能看出来各自的作用:
这里获取到的三个 AutoConfigurationImportFilter 过滤器其实就是上面这三个。接下来执行 filter 方法,如下:
List filter(List configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
这里就是遍历这三个过滤器,然后分别调用各自的 match 方法和 144 个自动化配置类进行匹配,如果这些自动化配置类所需要的条件得到满足,则 match 数组对应的位置就为 true,否则就为 false。
然后遍历 match 数组,将不满足条件的自动化配置类置为 null,最后再把这些 null 移除掉。
这样就获取到了我们需要进行自动化配置的类了。
最后一句 fireAutoConfigurationImportEvents 则是触发自动化配置类导入事件,这个没啥好说的~
当这些自动化配置类加载进来之后,接下来就是各种条件注解来决定这些配置类是否生效了,这些都比较简单了,之前在 vhr 种也和小伙伴们讲过多次了,这里就不再啰嗦了~
当前标题:SpringBoot启动注解分析
分享网址:http://www.shufengxianlan.com/qtweb/news42/44242.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联