【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.1
    port: 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容器中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决el-table组件中,分页后数据的勾选、回显问题?

问题描述&#xff1a; 1、记录一个弹窗点击确定按钮后&#xff0c;table列表所有勾选的数据信息2、再次打开弹窗&#xff0c;回显勾选所有保存的数据信息3、遇到的bug&#xff1a;切换分页&#xff0c;其他页面勾选的数据丢失&#xff1b;点击确认只保存当前页的数据&#xff1…

迅为RK3588开发板瑞芯微国产化工业ARM核心板AI人工智能

性能强 iTOP-3588开发板采用瑞芯微RK3588处理器&#xff0c;是全新一代AloT高端应用芯片&#xff0c;采用8nm LP制程&#xff0c;搭载八核64位CPU&#xff0c;四核Cortex-A76和四核Cortex-A55架构&#xff0c;主频高达2.4GHz&#xff0c;8GB内存&#xff0c;32GB EMMC。 四核心…

ACL和NAT

文章目录 ACL和NAT一、ACL概述及产生背景1、ACL访问控制列表2、ACL工作原理3、ACL种类4、ACL命令配置步骤4.1 ACL命令配置4.1 ACL配置步骤 二、NAT&#xff08;网络地址转换&#xff09;1、NAT概述2、NAT类型2.1 静态NAT与动态NAT 3、NATPT&#xff08;端口映射&#xff09;4、…

成都工业学院Web技术基础(WEB)实验二:HTML5表格、表单标签的使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

Arduino使用定时器设置周期时间运行程序

1、用Arduino millis() 函数 实现一定程度上的多任务系统&#xff0c;可以设置不同时间的任务周期去执行对应的程序。比如需要10毫秒执行一次的程序、100毫秒执行一次的程序、1秒执行一次的程序。 2、Delay(ms)是延时函数&#xff0c;使用该延时函数&#xff0c;后面的程序将会…

租一台服务器多少钱决定服务器的价格因素有哪些

租一台服务器多少钱决定服务器的价格因素有哪些 大家好我是艾西&#xff0c;服务器这个名词对于不从业网络行业的人们看说肯定还是比较陌生的。在21世纪这个时代发展迅速的年代服务器在现实生活中是不可缺少的一环&#xff0c;平时大家上网浏览自己想要查询的信息等都是需要服…

统信UOS上图形化配置系统和应用代理

原文链接&#xff1a;统信UOS上图形化配置系统和应用代理 hello&#xff0c;大家好啊&#xff0c;今天我要给大家介绍的是在统信UOS操作系统上如何通过图形化界面配置系统代理和应用代理。在许多公司的内网环境中&#xff0c;直接访问互联网可能受到限制&#xff0c;但通常会提…

关于linux 磁盘占用排查问题

1.关于磁盘 查看整体磁盘占用大小 df -h 2. 先排除mysql 数据大小 查询库的大小 SELECT table_schema AS "Database", ROUND(SUM(data_length index_length) / 1024 / 1024, 2) AS "Size (MB)" FROM information_schema.TABLES GROUP BY table_schema…

ACL与NAT

目录 一、ACL &#xff08;一&#xff09;ACL基本理论 &#xff08;二&#xff09;ACL的类型 1.基本ACL 2.高级ACL 3.二层ACL &#xff08;三&#xff09;基本原理 &#xff08;四&#xff09;项目实验 通配符掩码 二、NAT &#xff08;一&#xff09;基本理论 &am…

【XR806开发板试用】+Linux小白上手开发笔记(2)——阿里云云方案

##0、前言 在之前文章中提到&#xff0c;在windows中搭建unbuntu对于新手小白来说非常不友好。因此一直在找解决方案&#xff0c;找到一条非常有意思的方案。希望对大家有点帮助。 1、环境搭建 方案核心————阿里云云 具体步骤如下&#xff1a; step1&#xff1a;注册。由…

【Python可视化系列】一文教会你绘制美观的柱状图(理论+源码)

一、前言 前面我详细介绍了如何绘制漂亮的折线图&#xff1a; 【Python可视化系列】一文彻底教会你绘制美观的折线图&#xff08;理论源码&#xff09; 本篇文章将教你绘制美观的柱状图。柱状图&#xff08;Bar Chart&#xff09;是一种常用的统计图表&#xff0c;用于展示不同…

Nginx 代理 MySQL 连接,并限制可访问IP

1.前言 我们的生产环境基本上都部署在云服务器上&#xff0c;例如应用服务器、MySQL服务器等。如果MySQL服务器直接暴露在公网&#xff0c;就会存在很大的风险&#xff0c;为了保证数据安全&#xff0c;MySQL服务器的端口是不对外开放的。 好巧不巧&#xff0c;线上业务遇到b…

自动化测试(一)配置selenium环境(带图文,防止踩坑)

目录 配置selenium环境 1. 安装setuptools 2. 安装selenium 3. 安装驱动 如何查看谷歌浏览器版本 上一章讲述了如何安装python环境&#xff0c;那么&#xff0c;这一章讲述的是&#xff0c;如何配置自动化测试&#xff08;selenium&#xff09;环境~吧&#x1f937;‍♀️…

设计模式详解---抽象工厂模式

继续前言&#xff0c;工厂模式中抽象工厂模式的讲解&#xff1a; 1. 前面的工厂模式有啥问题&#xff1f; 前面的工厂模式有这么个问题&#xff1a;一个产品就给了一个工厂&#xff0c;这样子如果产品变多&#xff0c;系统就会很复杂&#xff1a; 2. 解决方法 我们可以按照手…

前端js实现将异步封装成promise然后用async await转同步

&#xff08;一&#xff09;需求背景&#xff1a; 哈喽 大家好啊&#xff0c;今天遇到一个问题&#xff0c;需要将异步请求转换成同步 &#xff08;二&#xff09;相关代码&#xff1a; function getInfo() {return new Promise((resolve,reject)> {setTimeout(()> {re…

vue_域名部署无法访问后端

前言 目前部署的比较另类&#xff0c;因为服务器为windows&#xff0c;目前还不是很会nginx&#xff0c;所以现在就只能在服务器上安装nodejs&#xff0c;然后直接使用npm run dev命令行的方式运行项目 遇到的坑 使用ip访问前端的时候&#xff0c;就可以访问&#xff0c;但是…

排序之归并排序

归并排序是第一个可以被实际使用的排序算法。归并排序性能不错&#xff0c;其复杂度为O(nlogn)。 归并排序是一种分治算法。其思想是将原始数组切分成较小的数组&#xff0c;直到每个小数组只有一 个位置&#xff0c;接着将小数组归并成较大的数组&#xff0c;直到最后只有一个…

获取Java类路径

利用System.getProperty(“java.class.path”)可以获取Java类路径&#xff08;Java class path&#xff09;。 package com.thb;import java.io.IOException;public class Test5 {public static void main(String[] args) throws IOException {System.out.println(System.getP…

pycharm在终端处删除连接过的服务器

目录 操作 操作 打开设置处的SSH配置进行删除