【Spring】@SpringBootApplication注解解析

news2024/12/22 19:41:06

前言:

        当我们第一次创建一个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容器中。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java--LinkedList集合的底层原理

1.什么是链表&#xff1f;有啥特点&#xff1f; ①链表中的结点是独立的对象&#xff0c;在内存中是不连续的&#xff0c;每个结点包含数据值和下一个结点的地址。 ②链表的特点1&#xff1a;查询慢&#xff0c;无论查询那个数据都要从头开始找。 ③链表的特点2&#xff1a;链…

day3_qt

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xf…

进程、容器与虚拟机的区别

进程、容器与虚拟机 参考&#xff1a;关于进程、容器与虚拟机的区别&#xff0c;你想知道的都在这&#xff01; 进程、容器与虚拟机的结构图 进程 介绍 进程是一个正在运行的程序&#xff0c;它是一个个可执行文件的实例。当一个可执行文件从硬盘加载到内存中的时候&#xf…

安装Anconda时出现Failed to extract packages的解决方法

目录 1. 问题所示2. 原理分析3. 解决方法 1. 问题所示 在win7安装Anconda的时候&#xff0c;出现Failed to extract packages 截图如下所示&#xff1a; 2. 原理分析 该版本过于新&#xff0c;无法兼容win7系统&#xff0c;要么更换系统 要么将anconda版本降低即可 3. 解决…

基于SSM超市订单管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

计算机二级Python基本操作题-序号46

Python 函数查询 1. 《卖火柴的小女孩》是丹麦童话故事作家安徒生的一篇童话故事&#xff0c;发表于1846年。主要讲了一个卖火柴的小女孩在富人阖家欢乐、举杯共庆的大年夜冻死在街头的故事。这里给出《卖火柴的小女孩》的一个网络版本文件&#xff0c;文件名为“小女孩.txt”…

IOday8作业

使用消息队列完成两个进程之间相互通信(多进程) #include<myhead.h>//定义结构体 struct buf {long mtype;char mtest[1024]; };#define SIZE (sizeof(struct buf)-sizeof(long))//进程 int main(int argc, const char *argv[]) {//创建keykey_t key1 ftok("/&quo…

关于北京医学sci论文翻译

在医学领域&#xff0c;翻译论文是一项非常重要的工作。医学论文的翻译需要准确、专业、严谨&#xff0c;同时也需要考虑到医学领域的特殊性和复杂性。那么&#xff0c;如何翻译医学论文呢&#xff1f;北京医学SCI论文翻译哪家好呢&#xff1f; 首先&#xff0c;需要具备专业的…

5.鸿蒙hap可以直接点击包安装吗?

5.鸿蒙hap可以直接点击包安装吗&#xff1f; hap与apk不同&#xff0c;获取的hap不能直接安装 安装方法1&#xff1a; DevEco studio打开项目源文件&#xff0c;打开手机USB调试&#xff0c;DevEco识别到手机后&#xff0c;点击播放按钮安装到手机 https://txwtech.blog.cs…

多线程案例-阻塞队列

阻塞队列是什么 阻塞队列是一种特殊的队列.也遵循"先进先出"的原则 阻塞队列能是一种线程安全的数据结构,并且具有以下特性: 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素. 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素…

小电流MOSFET 选型分析数据,可应用于电子烟,电动工具,智能穿戴等产品上

小电流双N&#xff0c;D-N通道MOSFET&#xff0c;电压60V-100V左右 电流300mA-500MA&#xff0c;采用封装形式多样。具有低导通电阻&#xff0c;可快速切换速度&#xff0c;易于设计的驱动电路也易于并联&#xff0c;ESD保护&#xff0c;低电压驱动使该器件非常适合便携式设备…

相控与斩控交交调压(THD的计算)

相控与斩控交交调压&#xff08;THD的计算&#xff09;

ML81N服务条目表创建审批

ML81N服务条目表创建 ME23N创建服务类采购订单 ML81N根据采购订单明细创建服务条目表 保存后采购订单会生成物料凭证 删除 创建 参考ESLL表&#xff0c;需要区分父包&#xff0c;子包&#xff1b;其中ls_esll-pln_pckg需要根据EKPO关联ESSR关联ESLL获取到原始包装编号 …

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;是一组常用来…

认识lambda架构(架构师考试复习)

Lambda架构主要分为三层&#xff0c;批处理层、加速层和服务层。 如下图所示&#xff1a; &#xff08;1&#xff09;批处理层&#xff08;Batch Layer&#xff09;&#xff1a;存储数据集&#xff0c;在数据集上预先计算查询函数&#xff0c;并构建查询对应的view。Batch Lay…

perl处理json的序列化和反序列化

perl可以使用JSON模块很方便的处理json的序列化和反序列化。先来一段简单的例子&#xff1a; #! /usr/bin/perl use v5.14; use JSON; use IO::File;my $info {id > 1024,desc > hello world,arry > [1, 2, 3, 4, 5],obj > {char > [ A, B, C ]} };say to_jso…

企业网站运营不稳定有什么影响

如果一个公司的网站打开都有困难&#xff0c;那么用户会对这个企业的实力产生怀疑&#xff0c;企业网站除了作为企业的名片外&#xff0c;更多的是承担增加企业交易订单的任务。因此很多网站会做有关的网络广告或者搜索引擎优化的工作。如果网站无法正常打开&#xff0c;那么用…

从零开发短视频电商 AWS OpenSearch Service开发环境申请以及Java客户端介绍

文章目录 创建域1.创建域2.输入配置部署选项数据节点网络精细访问控制访问策略 获取域端点数据如何插入到OpenSearch ServiceJava连接OpenSearch Servicespring-data-opensearchelasticsearch-rest-high-level-clientopensearch-rest-clientopensearch-java 因为是开发测试使用…

视频中自监督学习:「我的世界」下指令理解与跟随

本文介绍了北京大学人工智能研究院梁一韬助理教授所带领的 CraftJarvis 团队在「我的世界」环境下探索通用智能体设计的新进展&#xff0c;题为“GROOT: Learning to Follow Instructions by Watching Gameplay Videos”。 ​ GROOT 该研究的核心目标是探索能否摆脱文本数据的标…

12.11 作业

1&#xff0c; 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码…