Spring 原理详解

1. Bean的作用域

Bean在Spring中表示的是Spring管理的对象,Bean的作用域是只Bean在Spring框架中的某种行为模式。

在Spring中,支持6中作用域:

  1. singleton:单例作用域,在整个 Spring IoC 容器中,只创建一个 Bean 实例。这是默认的作用域。
  2. prototype:原型作用域,每次获取 Bean 时都会创建一个新的实例。
  3. request:请求作用域,在一次 HTTP 请求中,创建一个 Bean 实例。该作用域仅适用于 WebApplicationContext 环境。
  4. session:会话作用域,在一次 HTTP Session 中,创建一个 Bean 实例。同样仅适用于 WebApplicationContext 环境。
  5. application:每个ServletContext生命周期内创建新的实例。
  6. websocket:HTTP WebSocket作用域。

后面4种在Spring MVC环境种才生效

 1.1 singleton

单例模式,在整个Spring IoC 容器中,只创建一个Bean的实例:

@Configuration
public class DogConfig {
    @Bean
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("小花");
        dog.setColor("黑白相间");
        return dog;
    }
}

以默认作用域注入一个Dog对象,通过Spring上下文/@Autowired 获取Bean:

@SpringBootTest
class J20240524SpringTheoryApplicationTests {
    @Autowired
    ApplicationContext context;
    @Test
    void contextLoads() {
        Dog dog1 = context.getBean("dog", Dog.class);
        Dog dog2 = context.getBean("dog", Dog.class);
        System.out.println(dog1);
        System.out.println(dog2);
    }
}

运行结果:

1.2 prototype

原型作用域,每次获取 Bean 时都会创建一个新的实例。

使用@Scope注解来指定Bean的作用域:

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog propertyDog() {
        return new Dog();
    }

    @Test
    void contextLoads1() {
        Dog dog1 = context.getBean("propertyDog", Dog.class);
        Dog dog2 = context.getBean("propertyDog", Dog.class);
        System.out.println(dog1);
        System.out.println(dog2);
    }

运行结果:

1.3 request

请求作用域,在一次 HTTP 请求中,创建一个 Bean 实例。

添加一个request作用域的Bean

    @Bean
    @RequestScope
    public Dog requestDog() {
        return new Dog();
    }

这里的@RequestScop注解相当于 @Scope("request")

    @RequestMapping("/request")
    public String request() {
        Dog dog1 = context.getBean("requestDog", Dog.class);
        Dog dog2 = context.getBean("requestDog", Dog.class);
        return "Autowired: " + dog1.toString() + "<br/>" + "context: " + dog2.toString();
    }

启动程序访问url:

可以看到一次请求中两次获取的Bean是同一个,当我们刷新页面(重新发送请求):

获取到的Bean和上次不同

1.4 session 

 会话作用域,在一次 HTTP Session 中,创建一个 Bean 实例。同样仅适用于 WebApplicationContext 环境。

    @Bean
    @SessionScope
    public Dog sessionDog() {
        return new Dog();
    }
    @RequestMapping("/session")
    public String session() {
        Dog dog1 = context.getBean("sessionDog", Dog.class);
        Dog dog2 = context.getBean("sessionDog", Dog.class);
        return "Autowired: " + dog1.toString() + "<br/>" + "context: " + dog2.toString();
    }

运行程序访问url:

即便我们重新访问,得到的也是同一个对象,因为请求在一个会话中,我们可以使用另一个浏览器来访问,此时服务器会认为这是两个不同的会话就能得到不同的对象:

 

1.5 application 

在一个应用中,多次访问都是同一个对象:

即使在不同会话中访问的也是同一个对象,和singleton作用域有些类似,但也有不同之处:

一个web容器中可能有多个应用程序,singleton作用域是针对于整个web容器来说只能有一个对象,application作用域则是对于每个应用程序中只能有一个对象。

2. Bean的生命周期

生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程叫做一个对象的生命周期,Bean的生命周期分为以下5个部分:

  1. 实例化:为Bean分配内存空间。
  2. 属性赋值:初始化属性,Bean注入和装配。
  3. 初始化:a.执行各种通知,如BeanNameAware,BeanFactoryAware的接口方法。b.执行初始化方法,初始化后置方法。
  4. 使用Bean
  5. 销毁Bean

初始化和销毁使用用户自定义扩展的两个阶段,可以在实例化之后,类加载之前进行自定义处理。

@Component
public class DogComponent implements BeanNameAware {
    public Dog singletonDog;
    public DogComponent() {
        System.out.println("执行构造方法实例化");
    }

    @Autowired
    public void setSingletonDog(Dog singletonDog) {
        this.singletonDog = singletonDog;
        System.out.println("执行setSingletonDog注入Bean");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("执行setBeanName, BeanName: " + name);
    }

    @PostConstruct
    public void init() {
        //@PostConstruct:表示该方法为初始化方法
        System.out.println("执行init方法初始化");
    }

    public void use() {
        System.out.println("执行use方法");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("执行destroy方法, 进行销毁前处理");
    }
}
@SpringBootTest
class DogComponentTest {
    @Autowired
    public DogComponent component;
    @Test
    void use() {
        component.use();
    }
}

执行测试代码:

3. Spring Boot 自动配置

 Spring Boot 自动配置就是当Spring容器启动后一些配置类,Bean对象等就自动存入到了IoC容器中,不需要手动去声明从而简化开发。

3.1 Spring 加载 Bean

通过我们之前的学习,我们知道,要把一个对象加载到IoC容器中需要给对应的类添加上五大注解或者使用@Bean注解,但是当我们想要把第三方的类对象添加到IoC容器中时,我们是无法给第三方的代码中添加注解的,我们之前的解决方案是在本地实现一个类,然后通过@Bean注解来添加,但是我们并不知道使用的第三方包需要添加哪些Bean,所以这样实现显然是不太科学的。

解决方案:

使用第三方jar包,需要配置哪些Bean,一定是第三方作者最清楚,那么就让第三方来完成添加Bean的操作:

package com.test.autoconfig;

public class CatConfig1 {
    public void use() {
        System.out.println("useCatConfig1");
    }
}
package com.test.autoconfig;

public class CatConfig2 {
    public void use() {
        System.out.println("useCatConfig2");
    }
}

假设com.test.autoconfig 是一个第三方包,这个第三方包中可以定义一个实现了ImportSelector接口的类在这个类中设置要添加哪些Bean:

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {
                "com.test.autoconfig.CatConfig1",
                "com.test.autoconfig.CatConfig2"
        };
    }
}

然后通过@Import注解添加到启动类上:

使⽤@Import导入的类会被Spring加载到IoC容器中

@Import(com.test.autoconfig.MyImportSelector.class)
@SpringBootApplication
public class J20240524SpringTheoryApplication {

    public static void main(String[] args) {
        SpringApplication.run(J20240524SpringTheoryApplication.class, args);
    }

}

但是这种形式需要使用者记得该jar包的哪个类是配置类,也不太方便,现在比较常见的方案是第三方提供一个注解,这个注解一般都是以@EnableXXX开头的注解,注解中封装@Import注解:

package com.test.autoconfig;


import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableCat {

}

然后在启动类上加上该注解即可:

@EnableCat
@SpringBootApplication
public class J20240524SpringTheoryApplication {

    public static void main(String[] args) {
        SpringApplication.run(J20240524SpringTheoryApplication.class, args);
    }

}

3.2 SpringBoot 源码分析

上面我们简单讲解了原理,接下来我们通过SpringBoot的源码来看看Spring是如何具体实现的。

在Spring项目的启动类上有一个@SpringBootApplication注解,显然这个注解就是关键所在,我们转到该注解的定义:

其中@ComponentScan注解我们知道它的功能是指定加载Bean时的扫描路径,但是这里并没有指定所以扫描路径是使用该注解的地方,也就是启动类所在目录和子目录。

再看@SpringBootConfiguration注解,我们转到其定义:

可以看到有五大注解的@Configuration 注解, 所以这个注解就是一个扩展了的@Configuration 注解,主要作用是把类交给Spring。

现在除了元注解就只剩下@EnableAutoConfiguration 注解,显然这个注解就是关键所在

最后再看@EnableAutoConfiguration 注解:

在这个注解中除了元注解,我们可以看到两个注解,我们重点关注这两个注解

3.2.1  AutoConfigurationImportSelector

@Import注解是用来导入Bean的,这个注解导入了一个Auto ConfigurationImportSelector类,我们转到这个类的实现:

在这个类中我们可以看到它实现了ImportSelector接口的 selectImports方法用来添加Bean,我们继续转到图中getAutoConfigurationEntry()方法的实现,显然该方法的作用是获取要导入的Bean的类的全限定名称:

仔细观看这个方法,发现该方法主要都是围绕configurations这个变量来处理的,所以我们直接点开getCandidateConfigurations()方法,查看configurations是如何生成的:

注意该方法的第三行,这里做了一个判断,如果configurations是空的,就会提示下面绿色字符串,这句话翻译过来的意思是:
在META-INF/spring/org.springframework.boot.autoconfigurationAutoConfiguration.中找不到自动配置类。如果您使用的是自定义包装,请确保该文件是正确的。

显然Bean的加载和META-INF/spring/org.springframework.boot.autoconfigurationAutoConfiguration.这个路径下的文件有关,我们双击shift查找该文件

在该文件中我们发现了非常多的全限定类名,显然该文件中存储了使用springBoot 需要添加的Bean的全限定类名,我们随便打开一个其中的类:

发现该类中就通过@Bean注解声明了一些需要交给Spring的Bean,这里我们发现,里面的类名都有红色的报错信息,这是因为我们没有引入对应的依赖,所以是找不到对应的类的。但是这个文件又是SpringBoot中默认带有的,也就是Spring会默认加载这个Bean,但是我们没有引入对应的jar包却没有报错,这是因为@ConditionalOnMissingBean注解,该注解会根据条件来决定是否要加载使用该注解的类中提供的Bean,如果没有引入对应的jar包自然也就不会加载,所以不会报错。

以上的流程是Spring加载默认Bean的方式/过程,但是问题又来了,如果一些Spring没有默认加载的第三方库也需要Spring来管理一些Bean呢?我们回到getAutoConfigurationEntry方法:

我们发现倒数第二行还有一个方法,通过该方法的名称我们猜测它也是自动配置相关功能的,我们转到实现:

 

继续转到实现:

 

最后来到这里:

我们发现,Spring又根据META-INF/spring.factories路径下的这个文件又加载了一些Bean,在Spring项目启动时,Spring会扫描所有的META-INF/spring.factories,包括第三方的jar包,也就是说,只要想让Spring管理Bean的第三方包,只需添加一个spring.factories文件在META-INF下,就可以让Spring来管理其中包含的类。

3.2.2 @AutoConfigurationPackage

 

在@EnableAutoConfiguration注解中,还有一个@AutoConfigurationPackage注解,我们转到实现:

该注解导入了一个类我们转到实现:

其中有一个名为registerBeanDefinitions的方法,如果使用debug查看,会发现这里传入的包是启动类的包。

它的作用是指定自动配置包的扫描起始位置,确保Spring Boot能够正确扫描到自动配置类并实现自动配置功能,即确保@Import(AutoConfigurationImportSelector.class)能被SpringBoot扫描到。

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

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

相关文章

Json差异比较

json差异比较 如何比较两个json的差异 代码实现 导入依赖 <dependency><groupId>cn.xiaoandcai</groupId><artifactId>json-diff</artifactId><!-- 旧版本可能存在某些缺陷。版本请以maven仓库最版为准。 --><version>4.1.3-RC1-R…

没想到,一个小妙招让桌面运维效率翻倍

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 我的网工朋友大家好。 咱们都知道&#xff0c;电脑用久了&#xff0c;总会出些小毛病&#xff0c;比如桌面图标不显示了&#xff0c;C盘又满了&a…

springboot+minio 文件上传

前期准备 需要先安装minio文件服务器&#xff0c;请参考我上一篇文章 pom.xml 版本 本次使用的是springboot2.7.16 版本&#xff0c; minio 版本是8.2.2 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-pare…

利用AI办公工具类API,大幅提高办公效率

AI办公工具类API是一项革命性的技术&#xff0c;利用人工智能的力量为办公场景提供了许多创新的解决方案。借助AI办公工具类API&#xff0c;用户可以实现自动化的文档处理、语音转文字、图像识别、数据分析等多种功能&#xff0c;大大提高了办公效率和工作质量。此外&#xff0…

Uni-App开发 导入(引入)Vant-Weapp组件;支持vue3/vue2版本和微信小程序

文章目录 目录 文章目录 操作流程 小结 概要安装流程技术细节小结 概要 Vant Weapp官网&#xff1a;Vant Weapp - 轻量、可靠的小程序 UI 组件库 准备工作&#xff0c;需要确保自己的电脑上已安装Hbuilde和node 全程操作的环境都需要这些配合才能运行上&#xff0c;可参考作者…

如何彻底搞懂组合(Composite)设计模式?

当我们在设计系统对象关系时&#xff0c;有时候会碰到这样一种场景&#xff0c;一个对象中包含了另一组对象&#xff0c;两者构成一种”部分-整体”的关联关系。 正如上图中所展示的&#xff0c;当我们面对这样一种对象关系时&#xff0c;通常都需要分别构建单独的访问方式&…

数据挖掘案例-航空公司客户价值分析

文章目录 1. 案例背景2. 分析方法与过程2.1 分析流程步骤2.2 分析过程1. 数据探索分析2. 描述性统计分析3. 分布分析1.客户基本信息分布分析2. 客户乘机信息分布分析3. 客户积分信息分布分析 4. 相关性分析 3. 数据预处理3.1 数据清洗3.2 属性约束3. 3 数据转换 4. 模型构建4. …

【面经】单片机

1、单片机IO口工作方式 输入 模拟输入&#xff08;GPIO_Mode_AIN&#xff09;&#xff1a;关闭施密特触发器&#xff0c;将电压信号传送到片上外设模块&#xff0c;通常用于连接模拟信号源。浮空输入&#xff08;GPIO_Mode_IN_FLOATING&#xff09;&#xff1a;在浮空输入状态…

回收站清空的文件怎么恢复?8个方法公开(2024更新版)

“我太粗心了&#xff0c;刚想恢复部分回收站中误删的重要文件&#xff0c;一不小心把回收站清空了&#xff0c;现在还有什么方法可以恢复它们吗&#xff1f;” 在数字时代&#xff0c;电脑已经成为我们日常生活和工作中不可或缺的工具。然而&#xff0c;随着我们对电脑的依赖加…

etcd 和 MongoDB 的混沌(故障注入)测试方法

最近在对一些自建的数据库 driver/client 基础库的健壮性做混沌&#xff08;故障&#xff09;测试, 去验证了解业务的故障处理机制和恢复时长. 主要涉及到了 MongoDB 和 etcd 这两个基础组件. 本文会介绍下相关的测试方法. MongoDB 中的故障测试 MongoDB 是比较世界上热门的文…

【算法】排序——加更

补充1个排序&#xff1a;希尔排序 思路&#xff1a;首先定义一个gap,从第0个数开始&#xff0c;每隔一个gap取出一个数&#xff0c;将取出来的数进行比较&#xff0c;方法类似插入排序。第二轮从第二个数开始&#xff0c;每隔一个gap取出一个数再进行插入排序。四轮就可以取完…

新手一次过软考高级(系统规划与管理师)秘笈,请收藏!

2024上软考已经圆满结束&#xff0c;距离下半年的考试也只剩下半年不到的时间。需要备考下半年软考高级的小伙伴们可以抓紧开始准备了&#xff0c;毕竟高级科目的难度可是不低的。 今天给大家整理了——系统规划与管理师的备考资料 &#xff0c;都是核心重点&#xff0c;有PDF&…

微博v14.5.1,集成猪手模块2.3.0-276,移除广告和各类推广提示

软件介绍 微博 v14.5.1&#xff0c;内置猪手模块直装版是一款专业优化的微消客户端&#xff0c;该软件融合了咸猪手模块&#xff0c;并提供了用户友好的自定义选项。这些选项包括广告移除、停止推荐内容、消除各类提示消息等功能&#xff0c;旨在提升用户的个性化使用体验。 …

最详细Linux提权总结(建议收藏)

1、内核漏洞脏牛提权 查看内核版本信息 uname -a 具体提权 1、信息收集配合kali提权 uname -a #查看内核版本信息 内核版本为3.2.78&#xff0c;那我们可以搜索该版本漏洞 searchsploit linux 3.2.78 找到几个可以使用的脏牛提权脚本&#xff0c;这里我使用的是40839.c脚…

Facebook广告如何开户以及投放费用?

Facebook作为全球最大的社交媒体平台之一&#xff0c;成为了企业与个人推广品牌、产品或服务的重要渠道。其精准的广告定向功能和庞大的用户基数&#xff0c;为广告主提供了无限的商机。云衔科技为企业提供专业的Facebook上开户和运营服务&#xff0c;助力您高效获客。 一、Fa…

【Spring Cloud】Feign整合服务容错中间件Sentinel

文章目录 引入sentinel依赖配置文件为被容错的接口指定容错类创建容错类修改controller演示扩展为被容错的接口更改容错类创建回退工厂类演示 总结 上一篇文章中我们已经对服务容错中间件 Sentinel 持久化的两种模式进行了全面解析&#xff0c;本文我们将对Feign和Sentinel进行…

学术图表的基本配色方法

不论是商业图表还是专业图表&#xff0c;图表的配色都极其关键。图表配色主要有彩色和黑白两种配色方案。刘万祥老师曾提出&#xff1a; “在我看来&#xff0c;普通图表与专业图表的差别&#xff0c;很大程度就体现在颜色运用上。” 对于科学图表&#xff0c;大部分国内的期…

lua 计算第几周

需求 计算当前赛季的开始和结束日期&#xff0c;2024年1月1日周一是第1周的开始&#xff0c;每两周是一个赛季。 lua代码 没有处理时区问题 local const 24 * 60 * 60 --一整天的时间戳 local server_time 1716595200--todo:修改服务器时间 local date os.date("*t…

利用阅读APP3.0目录展示要查看的内容02

要实现前面提到的功能并不困难&#xff0c;只要导入如下规则即可: 打开APP导入对应规则: 导入后的目录规则界面: 导入后的替换规则界面: 规则文件详细内容: 1. 目录规则&#xff1a; 2. 替换规则 除了直接导入上述文件&#xff0c;也可以自己添加规则。总之&#xff0c;就是利用…

蓝桥杯第十四届国赛B组刷题笔记

A-0子2023&#xff1a; 题目&#xff1a; 小蓝在黑板上连续写下从 11 到 20232023 之间所有的整数&#xff0c;得到了一个数字序列&#xff1a; &#x1d446;12345678910111213...20222023S12345678910111213...20222023。 小蓝想知道 &#x1d446;S 中有多少种子序列恰好等…