面试官:你说你精通SpringBoot,你给我说一下类的自动装配吧
## 剖析@SpringBootApplication注解
创建一个SpringBoot工程后,SpringBoot会为用户提供一个Application类,该类负责项目的启动:
```
@SpringBootApplication
public class SpringbootSeniorApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSeniorApplication.class, args);
}
}
````
这是一个被`@SpringBootApplication`注解的类,该注解完成了SpringBoot中类的自动装配任务:
```
@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继承了三个注解:
## @SpringBootConfiguration
```
/**
* Indicates that a class provides Spring Boot application
* {@link Configuration @Configuration}. Can be used as an
* alternative to the Spring's standard @Configuration
* annotation so that configuration can be found
* automatically (for example in tests).
*
* Application should only ever include one
* @SpringBootConfiguration and most idiomatic Spring Boot
* applications will inherit it from @SpringBootApplication.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
```
在说明中提到,`@SpringBootConfiguration`注解是用来替代Spring的`@Configuration`,方便SpringBoot自动找到配置。
## @ComponentScan
````
/**
* Configures component scanning directives
* for use with Configuration classes.
* Provides support parallel with Spring XML's
* <context:component-scan> element.
*
* Either #basePackageClasses or #basePackages
* (or its alias #value} may be specified to
* define specific packages to scan. If specific
* packages are not defined, scanning will occur
* from the package of the class that declares
* this annotation.
*
* Note that the <context:component-scan> element
* has an annotation-config attribute; however,
* this annotation does not. This is because
* in almost all cases when using @ComponentScan,
* default annotation config processing
* (e.g. processing @Autowired and friends)
* is assumed. Furthermore, when using
* AnnotationConfigApplicationContext,
* annotation config processors are always
* registered, meaning that any attempt to disable
* them at the @ComponentScan level would be ignored.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
...
}
````
在说明中我们可以得知:`@ComponentScan`只负责指定要扫描的包,并没有装配其中的类,这个真正装配这些类是`@EnableAutoConfiguration`。
## @EnableAutoConfiguration
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
```
该类真正完成了SpringBoot对于类的装配工作,具体内容在后续会作出解释。
## 以@Enable开头的注解
以@Enable开头的注解(`@EnableXxx`)一般用于开启某一项功能,是为了简化代码的导入。它是一个组合注解,一般情况下`@EnableXxx`注解中都会组合一个`@Import`注解,而该`@Import`注解用于导入指定的类,而被导入的类一般有三种:
## 配置类
* 类的特征:@Import中指定的类一般以Configuration结尾
* 类的配置:该类上会注解@Configuration
* 类的案例:定时任务启动注解:`SchedulingConfiguration`
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
```
## 选择器
* 类的特征:@Import中指定的类一般以 Selector 结尾
* 类的配置:该类直接或间接实现了`ImportSelector`接口,表示当前类会根据条件选择导入不同的类。
* 类的案例:Redis配置类:`CachingConfigurationSelector`
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
...
}
```
```
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
...
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
...
}
```
## 注册器
* 类的特征:@Import 中指定的类一般以 Registrar 结尾。
* 类的配置:该类直接或间接实现了`ImportBeanDefinitionRegistrar`接口,用于导入注册器,该类可以在代码运行时动态注册指定类的实例。
* 类的案例:AspectJ:`AspectJAutoProxyRegistrar`
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
...
}
```
```
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
```
## 解析@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 {};
}
```
该注解是一个组合注解,用于完成自动配置,它是Spring Boot的核心注解。所谓自动配置是指,将用户自定义的类及框架本身用到的类进行装配。
## @AutoConfigurationPackage
```
/**
* Registers packages with AutoConfigurationPackages.
* When no #basePackages base packages or
* #basePackageClasses base package classes are
* specified, the package of the annotated class is
* registered.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}
```
从类的说明中我的得知,该注解用于导入并装配用户自定义类,即自动扫描包中的类。若该注解未通过`basePackages`或`basePackageClasses`参数指明要扫描的包路径,则默认扫描含该注解的类所在包及其子包。
## @Import
用于导入并装配框架本身的类。其参数`AutoConfigurationImportSelector.java`类,该类用于导入自动配置的类。其装配跟踪入口:`#getCandidateConfigurations`
```
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware,
EnvironmentAware, Ordered {
...
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;
}
...
}
```
`#getCandidateConfigurations` -> `SpringFactoriesLoader.loadFactoryNames`
```
public final class SpringFactoriesLoader {
...
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
...
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
...
} catch (IOException ex) {
...
}
}
}
```
追踪到这里,我们得知,框架本身定义的类是从`META-INF/spring.factories`文件中获取的。该文件目录在哪儿呢?
在创建SpringBoot Web项目时,我们在pom.xml文件中会自动导入一个依赖:
```
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
```
打开一个starter,如`spring-boot-starter-web`依赖,我们可以看到其中包含了一个子依赖:
```
<!-- spring-boot-starter-web-2.3.4.RELEASE.pom -->
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
...
</dependencies>
```
打开`spring-boot-starter`依赖,可以看到这么一个子依赖:
```
<!-- spring-boot-starter-2.3.4.RELEASE.pom -->
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
...
</dependencies>
```
查看该依赖的内容,打开spring.factories文件:
![](https://upload-images.jianshu.io/upload_images/23140115-7a032a14b5816253.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://upload-images.jianshu.io/upload_images/23140115-4f7429a7e416bec2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这些就是框架定义的,需要装配的类。
## application.yml的加载
`application.yml`文件对于 Spring Boot 来说是核心配置文件,至关重要!那么,该文件是如何加载到内存的呢?我们需要从启动类的`run()`方法开始跟踪,该跟踪过程比较深,耐心差的读者慎入。
```
@SpringBootApplication
public class SpringbootSeniorApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSeniorApplication.class, args);
}
}
```
进入run方法:
```
public class SpringApplication {
...
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
...
// 准备运行环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
...
}
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
...
// 让监听器监听环境准备过程
listeners.environmentPrepared(environment);
...
}
...
}
```
让监听器监听环境准备过程
```
class SpringApplicationRunListeners {
...
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
...
}
```
发布环境准备事件
```
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
...
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(
this.application,
this.args,
environment
)
);
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
// 触发监听器
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
...
}
```
触发监听器
```
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
...
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
...
listener.onApplicationEvent(event);
...
}
...
}
```
`ApplicationListener#onApplicationEvent`是一个接口方法,我们主要看它的`ConfigFileApplicationListener`实现类的实现
```
public class ConfigFileApplicationListener implements ... {
...
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
...
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
...
}
```
`EnvironmentPostProcessor#postProcessEnvironment`是一个接口方法,我们主要看它的`ConfigFileApplicationListener`实现类的实现
```
public class ConfigFileApplicationListener implements ... {
...
@Override
public void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application) {
// 加载配置文件
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(
ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
private class Loader {
void load() {
FilteredPropertySource.apply(
this.environment,
DEFAULT_PROPERTIES,
LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
...
while (!this.profiles.isEmpty()) {
...
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
...
}
...
});
}
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
...
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
}
}
}
private void loadForFileExtension(
PropertySourceLoader loader,
String prefix,
String fileExtension,
Profile profile,
DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
...
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
private void load(
PropertySourceLoader loader,
String location,
Profile profile,
DocumentFilter filter,
DocumentConsumer consumer) {
...
List<Document> documents = loadDocuments(loader, name, resource);
...
}
private List<Document> loadDocuments(
PropertySourceLoader loader,
String name,
Resource resource) throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
}
...
}
```
`PropertySourceLoader#getFileExtensions`和`PropertySourceLoader#load`都是接口方法,我们主要看它的`YamlPropertySourceLoader`实现类的实现
```
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" };
}
@Override
public List<PropertySource<?>> load(
String name,
Resource resource) throws IOException {
...
return propertySources;
}
}
```
## 最后
感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞!
面试官:你说你精通SpringBoot,你给我说一下类的自动装配吧的更多相关文章
- SpringBoot:带你认认真真梳理一遍自动装配原理
前言 Spring翻译为中文是“春天”,的确,在某段时间内,它给Java开发人员带来过春天,但是随着我们项目规模的扩大,Spring需要配置的地方就越来越多,夸张点说,“配置两小时,Coding五分钟 ...
- 面试官:给我讲讲SpringBoot的依赖管理和自动配置?
1.前言 从Spring转到SpringBoot的xdm应该都有这个感受,以前整合Spring + MyBatis + SpringMVC我们需要写一大堆的配置文件,堪称配置文件地狱,我们还要在pom ...
- 阿里面试:问springBoot自动装配我这样回答的,面试官对我竖起了大拇指
引言 最近有个读者在面试,面试中被问到了这样一个问题"看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?"这应该是一个springboot里面 ...
- 面试官:你说你精通源码,那你知道ArrayList 源码的设计思路吗?
Arraylist源码分析 ArrayList 我们几乎每天都会使用到,但是通常情况下我们只是知道如何去使用,至于其内部是怎么实现的我们不关心,但是有些时候面试官就喜欢问与ArrayList 的源码相 ...
- Springboot启动扩展点超详细总结,再也不怕面试官问了
1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片.Springboot更是封装了Spring,遵循约定大于配置,加上自动装配 ...
- 面试官:聊一聊SpringBoot服务监控机制
目录 前言 SpringBoot 监控 HTTP Endpoints 监控 内置端点 health 端点 loggers 端点 metrics 端点 自定义监控端点 自定义监控端点常用注解 来,一起写 ...
- 我是面试官--"自我介绍"
工作10余年,经历过很多次面试,也面试了N多人.这些年来,已经有好些位朋友(或同事)与我聊起相关话题,涉及面试,更关乎职业生涯规划.感触颇多,就借助自媒体的浪潮,与更多的程序员一起共谈面试经历,希望可 ...
- iOS开发,这样写简历才能让大厂面试官看重你!
前言: 对于职场来说,简历就如同门面.若是没想好,出了差错,耽误些时日倒不打紧,便是这简历入不了HR的眼,费力伤神还不能觅得好去处,这数年来勤学苦练的大好光阴,岂不辜负? 简历,简而有力.是对一个人工 ...
- 【测试工程师面试】在BOSS直聘上和面试官的一问一答
岗位描述: 信用卡核心系统功能测试,负责测试计划制定,测试设计,测试执行,测试进度掌控,自动化工具建设等工作.有责任心,执行力强,工作认真细致,逻辑思维强熟悉linux,oracle或者IBM大型机操 ...
随机推荐
- java: 非法字符: '\ufeff'
错误问题记录: Error:(1, 1) java: 非法字符: '\ufeff' Error:(1, 1) 错误: 需要class, interface或enum报错 问题发生时因为编码问题导致,如 ...
- C语言单文件模板
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <stri ...
- Python合集之Python开发环境在Windows系统里面搭建
在上一个合集里面我们了解到了Python的基础信息及学习了Python对我们有什么用处,那么今天我们来了解一下,Python的开发环境该如何搭建.(注:Python的开发环境可以在Windows.MA ...
- Hive源码解析
date: 2020-07-08 15:12:00 updated: 2020-08-21 17:38:00 Hive源码解析 入口:hive-cli-1.1.0-cdh5.14.4.jar!/org ...
- Win32之创建进程
CreateProcess函数介绍 BOOL CreateProcessA( LPCSTR lpApplicationName, //可执行文件的名称完整的路径+程序名字) LPSTR lpComma ...
- 《Head First 设计模式》:与设计模式相处
正文 一.设计原则 1.封装变化 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起. 2.针对接口编程,不针对实现编程 "针对接口编程"真正的意思是& ...
- [Luogu P2257] YY的GCD (莫比乌斯函数)
题面 传送门:洛咕 Solution 推到自闭,我好菜啊 显然,这题让我们求: \(\large \sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)\in prime]\) 根 ...
- Redis的介绍及使用
redis 简介 简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向.另外,redis 也经常 ...
- Javasript中this指向问题和改变this指向的方法
在学习javascript中我们往往会被this的指向问题弄的头昏转向,今天我们就来学习一下this的指向问题,和改变this指向的方法. 一.this的指向问题 在学习this的指向问题之前我们需要 ...
- 一个名为不安全的类Unsafe
最近为了更加深入了解NIO的实现原理,学习NIO的源码时,遇到了一个问题.即在WindowsSelectorImpl中的 pollWrapper属性,当我点进去查看它的PollArrayWrapper ...