既然你精通SpringBoot,那给我说一下类的自动装配吧!

大家好,我是Java大蜗牛,一个渴望在互联网行业做到很牛的蜗牛。

可柔可刚,点赞则柔,白嫖则刚!死鬼~~~看完记得给我来个三连哦!

剖析@SpringBootApplication注解

创建一个SpringBoot工程后,SpringBoot会为用户提供一个Application类,该类负责项目的启动:

@SpringBootApplication

publicclassSpringbootSeniorApplication{

publicstaticvoidmain(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@interfaceSpringBootApplication {

}

抛却元注解不谈,@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@interfaceSpringBootConfiguration {

...}

在说明中提到, @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@interfaceComponentScan {

...}

在说明中我们可以得知: @ComponentScan 只负责指定要扫描的包,并没有装配其中的类,这个真正装配这些类是 @EnableAutoConfiguration 。

@EnableAutoConfiguration

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(AutoConfigurationImportSelector.class)

public@interfaceEnableAutoConfiguration {

...}

该类真正完成了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@interfaceEnableAutoConfiguration {

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@interfaceAutoConfigurationPackage {

...}

从类的说明中我的得知,该注解用于导入并装配用户自定义类,即自动扫描包中的类。若该注解未通过 basePackages 或 basePackageClasses 参数指明要扫描的包路径,则默认扫描含该注解的类所在包及其子包。

@Import

用于导入并装配框架本身的类。其参数 AutoConfigurationImportSelector.java 类,该类用于导入自动配置的类。其装配跟踪入口: #getCandidateConfigurations

publicclassAutoConfigurationImportSelectorimplements

DeferredImportSelector,BeanClassLoaderAware,

ResourceLoaderAware,BeanFactoryAware,

EnvironmentAware,Ordered {

    ...

    protected ListgetCandidateConfigurations(

AnnotationMetadata metadata,            AnnotationAttributes attributes) {        List 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.");

returnconfigurations;

}    ...}

#getCandidateConfigurations -> SpringFactoriesLoader.loadFactoryNames

publicfinalclassSpringFactoriesLoader{

...publicstaticfinalString FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";

...publicstaticList loadFactoryNames(ClassfactoryType, @NullableClassLoaderclassLoader){

String factoryTypeName = factoryType.getName();returnloadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());

}privatestaticMap> loadSpringFactories(@Nullable ClassLoader classLoader) {

...try{

Enumeration 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 -->

org.springframework.boot

spring-boot-starter-web

打开一个starter,如 spring-boot-starter-web 依赖,我们可以看到其中包含了一个子依赖:

<!-- spring-boot-starter-web-2.3.4.RELEASE.pom -->

...

org.springframework.boot

spring-boot-starter

2.3.4.RELEASE

compile

...

打开 spring-boot-starter 依赖,可以看到这么一个子依赖:

<!-- spring-boot-starter-2.3.4.RELEASE.pom -->

...

org.springframework.boot

spring-boot-autoconfigure

2.3.4.RELEASE

compile

...

查看该依赖的内容,打开spring.factories文件:

这些就是框架定义的,需要装配的类。

application.yml的加载

application.yml 文件对于 Spring Boot 来说是核心配置文件,至关重要!那么,该文件是如何加载到内存的呢?我们需要从启动类的 run() 方法开始跟踪,该跟踪过程比较深,耐心差的读者慎入。

@SpringBootApplication

publicclassSpringbootSeniorApplication{

publicstaticvoidmain(String[] args){

SpringApplication.run(SpringbootSeniorApplication.class,args);

}}

进入run方法:

publicclassSpringApplication{

...publicstaticConfigurableApplicationContext run(ClassprimarySource,String...args){

returnrun(newClass[]{ primarySource }, args);

}publicstaticConfigurableApplicationContext run(Class[]primarySources,String[]args){

returnnewSpringApplication(primarySources).run(args);

}publicConfigurableApplicationContext run(String... args) {

...// 准备运行环境

        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

        ...

    }

    private ConfigurableEnvironment prepareEnvironment(

            SpringApplicationRunListeners listeners,

            ApplicationArguments applicationArguments) {

        ...

        // 让监听器监听环境准备过程

        listeners.environmentPrepared(environment);

        ...

    }

    ...

}

让监听器监听环境准备过程

classSpringApplicationRunListeners{

...voidenvironmentPrepared(ConfigurableEnvironment environment){

for(SpringApplicationRunListener listener :this.listeners) {

listener.environmentPrepared(environment);        }    }    ...}

发布环境准备事件

publicclassEventPublishingRunListenerimplementsSpringApplicationRunListener,Ordered{

...    @Override

publicvoidenvironmentPrepared(ConfigurableEnvironment environment){

this.initialMulticaster.multicastEvent(

newApplicationEnvironmentPreparedEvent(

this.application,

this.args,

environment            )        );    }    @Override

publicvoidmulticastEvent(ApplicationEventevent){

multicastEvent(event, resolveDefaultEventType(event));

}    @Override

publicvoidmulticastEvent(final ApplicationEventevent, @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);

            }

        }

    }

    ...

}

触发监听器

publicclassSimpleApplicationEventMulticasterextendsAbstractApplicationEventMulticaster{

...protectedvoidinvokeListener(ApplicationListener listener, ApplicationEventevent){

ErrorHandler errorHandler = getErrorHandler();if(errorHandler !=null) {

try{

doInvokeListener(listener,event);

}catch(Throwable err) {

errorHandler.handleError(err);            }        }else{

doInvokeListener(listener,event);

}    }privatevoiddoInvokeListener(ApplicationListener listener, ApplicationEventevent){

...        listener.onApplicationEvent(event);

...    }    ...}

ApplicationListener#onApplicationEvent 是一个接口方法,我们主要看它的 ConfigFileApplicationListener 实现类的实现

publicclassConfigFileApplicationListenerimplements...{

...@Override

publicvoidonApplicationEvent(ApplicationEvent event){

if(eventinstanceofApplicationEnvironmentPreparedEvent) {

onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);        }        ...    }privatevoidonApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event){

List 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 {        voidload() {

FilteredPropertySource.apply(                this.environment,                DEFAULT_PROPERTIES,                LOAD_FILTERED_PROPERTY,                (defaultProperties) -> {                    ...while(!this.profiles.isEmpty()) {

...load(profile, this::getPositiveProfileFilter,

addToLoaded(MutablePropertySources::addLast,false));

...                    }                    ...                });        }        private voidload(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {

getSearchLocations().forEach((location) -> {                boolean isDirectory = location.endsWith("/");

Set names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;                names.forEach((name) ->load(location, name, profile, filterFactory, consumer));

});        }        private voidload(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 voidload(

PropertySourceLoader loader,                String location,                Profile profile,                DocumentFilter filter,                DocumentConsumer consumer) {            ...            List documents = loadDocuments(loader, name, resource);            ...        }        private List loadDocuments(                PropertySourceLoader loader,                String name,                Resource resource) throws IOException {            DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);            List documents = this.loadDocumentsCache.get(cacheKey);if(documents == null) {

List>loaded= loader.load(name, resource);

documents = asDocuments(loaded);

this.loadDocumentsCache.put(cacheKey, documents);            }returndocuments;

}    }    ...}

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;

    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343