【Spring】@SpringBootApplication注解解析

前言:

        当我们第一次创建一个springboot工程时,我们会对启动类(xxxApplication)有许多困惑,为什么只要运行启动类我们在项目中自定义的bean无需配置类配置,扫描就能自动注入到IOC容器中?为什么我们在pom文件中引入starter就可以自动的将第三方组件注册到IOC容器中?这些困惑的答案就是本文的答案

@SpringBootApplication注解:

        谈及启动类,肯定绕不开@SpringBootApplication注解,这个注解是干什么的?有什么用?只有知晓其组成成分我们才能解答这些问题。

        (1)@SpringBootApplication注解组成

@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 {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class,attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

        @ComponentScan注解相信大家都很熟悉了,它的作用就是扫描指定的package或者类,将所有加了@Component@Service@Repository@Controller@Configuration注解的类对象配置成bean,加载到IOC容器中统一管理。

        @ComponentScan注解使用

@Configuration
@ComponentScan(basePackages = {"com.hammajang.annotations.service",
"com.hammajang.annotations.controller"},
basePackageClasses = User.class)
public class Application{public static void main(String[] args){Application.run(Application.class,args);}
}

       在上述例子中,Spring会扫描com.hammajang.annotations下的service包、controller包,将这些包中配置成bean的类加载到IOC容器中,同时还会扫描User类,如果它也配置成bean,那么也会加载到容器中。

        补充

        @SpringBootConfiguration组成

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

        可以看到里面嵌套了一个@Configuration注解,这表明启动类也是一个配置类,也会被注册成bean加载到容器中。

   

@EnableAutoConfiguration注解:

        在基于Springboot搭建的项目中,如果我们想引入redis作为缓存中间件只需要三步

        (1)在pom文件引入redis的starter依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

        (2)在yaml文件配置redis服务主机地址、端口等信息

spring:redis:host: 127.0.0.1port: 6379

        (3)在业务层注入redis客户端对象

@Resource
StringRedisTemplate redisTemplate;

        在整个过程中,我们只引入了redis相关jar包、配置了redis信息,但没有将redis客户端对象配置成bean,那Spring是如何将这些第三方组件基于配置文件配置成bean并加载到IOC容器中的?

        先来看@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 {};
}

        @AutoConfigurationPackage作用指定Spring扫描的包范围,默认是标注了该注解的类(启动类)所在的包路径,将启动类包路径下的所有bean加载到IOC容器中。

        @Import(AutoConfigurationImportSelector.class)作用将第三方的自动配置类加载到IOC容器中。

        那么第一个疑问我们已经有答案了@SpringBootApplication是一个复合注解,其中包含了@ComponentScan注解以及@AutoConfigurationPackage,这两个注解共同作用,默认扫描当前包(启动类所在包)及其子包,将配置的bean加载到IOC容器中。

        接下来我们再聚焦于AutoConfigurationImportSelector这个类,这个类含有一个很重要的方法:selectImports()

public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);//返回值:需要注册到IOC容器中的类的全路径名数组//如:["com.hammajang.entity.User","com.hammajang.service.UserServiceImpl"]return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}

        再来看获取字符串数组的getAutoConfigurationEntry()方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//检查是否启用了自动配置if (!this.isEnabled(annotationMetadata)) {//没有启用则返回空return EMPTY_ENTRY;} else {//启用了自动配置,读取注解所有属性信息AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//获取候选配置//加载spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值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);}}

        在getCandidateConfigurations()方法处打上断点,启动Springboot工程:

        可以看到List数组存储了很多自动配置类的全路径名;再来看看getCandidateConfigurations()方法实现:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 使用 SpringFactoriesLoader 从 META-INF/spring.factories 文件中获取自动配置类的全路径名List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;}

        进入autoconfigure的spring.factories文件中我们可以看到如下配置信息:

        最后我们再来看一下SpringFactoriesLoader是如何读取每个starter依赖中spring.factories的配置信息的:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {//通过类加载器获取所有starter中spring.factories文件的URL资源列表Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");// 创建一个多值映射,用于保存工厂类型和实现类的映射关系LinkedMultiValueMap result = new LinkedMultiValueMap();//迭代资源列表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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;//遍历工厂实现类数组,将映射关系添加到结果中for(int var11 = 0; var11 < var10; ++var11) {String factoryImplementationName = var9[var11];result.add(factoryTypeName, factoryImplementationName.trim());}}}//将加载的映射关系放入缓存中cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}}

        那么第三方组件如何自动注入到IOC容器的疑问我们也有答案了

        (1)Springboot通过@Import注解将AutoConfigurationImportSelector类注入到IOC容器中。

        (2)AutoConfigurationImportSelector实现了ImportSelector接口,其中有一个selectImports()方法用于导入自动配置类。

        (3)Springboot通过SpringFactoriesLoader加载每一个starter中的spring.factories文件,获取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value值,再通过反射将自动装配类(xxxAutoConfiguraion)加载到IOC容器中。

        (4)最后再通过自动装配类的配置信息,将第三方组件配置成bean加载到IOC容器中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/266216.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法-Rust 版读书笔记-2线性数据结构-双端队列

数据结构与算法-Rust 版读书笔记-2线性数据结构-双端队列 1、双端队列 deque又称为双端队列&#xff0c;双端队列是与队列类似的项的有序集合。deque有两个端部&#xff1a;首端和尾端。deque不同于队列的地方就在于项的添加和删除是不受限制的&#xff0c;既可以从首尾两端添…

基于JavaWeb+SpringBoot+Vue在线拍卖系统的设计和实现

基于JavaWebSpringBootVue在线拍卖系统系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 摘 要 1 Abstract 1 1 系统概述 4 1.1 概述 4 1.2课题意义 4 1.3 主要内容 4 2 …

二叉树题目:在受污染的二叉树中查找元素

文章目录 题目标题和出处难度题目描述要求示例数据范围 前言解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;在受污染的二叉树中查找元素 出处&#xff1a;1261. 在受污染的二叉树中查找元素 难度 5 级 题目描述 要求…

12.11

1.q&#xff0c;w&#xff0c;e亮led1&#xff0c;2&#xff0c;3&#xff1b; a&#xff0c;s&#xff0c;d灭led1&#xff0c;2&#xff0c;3&#xff1b; main.c #include "uar1.h"#include "led.h"void delay(int ms){int i,j;for(i0;i<ms;i){for…

WampServer本地部署结合内网穿透实现公网访问本地服务

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

Altair Radioss碰撞 安全与冲击 衡祖仿真

Altair Radioss是解决瞬态加载工况下非线性问题的领先的结构分析求解器。其具备高扩展性、高品质、高鲁棒性&#xff0c;以及诸多功能&#xff1a;多域求解技术、高级材料功能(复合材料)等。Radioss求解器被广泛应用于汽车、航空航天、电子/家电、包装、轨道机车、生物医疗、能…

GoEasy使用手册

GoEasy官网 登录 - GoEasy 即时通讯聊天案例 GoEasy - GoEasy (gitee.com) 注意事项 接口使用人数上限为15&#xff0c;超出之后会请求超时返回408状态码&#xff0c;可以新建一个应用用来更换common Key 创建应用 ​ 添加应用名称&#xff0c;其余默认&#xff0c;点击…

【计算机网络】UDP报文详解

目录 一. UDP协议概述 二. UDP报文格式 首部 三. UDP的缓冲区 一. UDP协议概述 UDP——用户数据报协议&#xff0c;是传输层的一个重要协议 基于UDP的应用层协议有&#xff1a;DNS&#xff0c;TFTP&#xff0c;SNMP&#xff0c;NTP 协议全称默认端口号DNSDomain Name Se…

向宇的博客免责声明

文章目录 前言起因个人叠甲关于原创讨论对大家的一些话对CSDN的话学习群参与人员最后完结 前言 大家好&#xff0c;其实这里就只是我拿来记录自己的学习笔记的&#xff0c;但是随着关注度越来越高&#xff0c;发现我不能像之前那么随意了&#xff0c;想分享什么写什么&#xf…

python期末简答题及答案,python期末题库和答案

本篇文章给大家谈谈python期末简答题及答案&#xff0c;以及python期末题库和答案&#xff0c;希望对各位有所帮助&#xff0c;不要忘了收藏本站喔。 期末复习判断题 &#xff08; √ &#xff09;Python变量名区分大小写,所以student和Student不是同一个变量。&#xff08; &…

王道数据结构课后代码题p150 第13——17 (c语言代码实现)

目录 13.p 和 q 分别为指向该二叉树中任意两个结点的指针&#xff0c;试编写算法 ANCESTOR(ROOT,P,q,r)&#xff0c;找到P和q的最近公共祖先结点 r 14.假设二叉树采用二叉链表存储结构&#xff0c;设计一个算法&#xff0c;求非空二叉树 b的宽度(即具有结点数最多的那一层的结点…

图的遍历(深度优先遍历 + 广度优先遍历)

目录 &#x1f33c;广度优先遍历 &#xff08;1&#xff09;邻接矩阵BFS &#xff08;2&#xff09;邻接表BFS &#xff08;3&#xff09;非连通图BFS &#xff08;4&#xff09;复杂度分析 &#x1f33c;深度优先遍历 &#xff08;1&#xff09;邻接矩阵的DFS &#x…