面试官:你说你精通SpringBoot,你给我说一下类的自动装配吧
2020-09-21 13:47
1626 查看
剖析@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) @Retenti 71f8 on(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文件:
这些就是框架定义的,需要装配的类。
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学习之自动装配
- SpringBoot的自动装配
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
- Springboot中如何为Quartz的Job自动装配Spring容器Bean
- 面试官:你们为什么要用Spring Boot啊,Spring Boot的自动装配是怎么实现的?
- springboot的自动装配原理
- SpringBoot的自动装配的原理解析(九)
- SpringBoot学习2---走向自动装配
- 在L0版本手机在自动灭屏前会先闪一下屏再灭屏
- JavaEE Spring框架学习笔记(理论学习之通过@Autowired 自动装配注解的使用)
- flex中文英文的交接处自动换行问题 (找了好久,备份一下)
- @Autowired自动装配
- 高级装配 —— 如何处理自动装配的歧义性?
- 基于注解自动装配Bean
- 关于SpringBoot中基于条件的自动装配
- spring自动装配bean
- Spring自动扫描装配bean
- 通过反射调用的类中使用Spring@Autowired自动装配的成员为空的解决办法
- 使用classPath自动扫描装配(修改名称)