Spring 原理详解

news2024/11/16 1:31:47

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扫描到。

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

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

相关文章

Alamofire常见GET/POST等请求方式的使用,响应直接为json

Alamofire 官方仓库地址&#xff1a;https://github.com/Alamofire/Alamofire xcode中安装和使用&#xff1a;swift网络库Alamofire的安装及简单使用&#xff0c;苹果开发必备-CSDN博客 Alamofire是一个基于Swift语言开发的优秀网络请求库。它封装了底层的网络请求工作&…

每日5题Day11 - LeetCode 51 - 55

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;51. N 皇后 - 力扣&#xff08;LeetCode&#xff09; class Solution {public List<List<String>> solveNQueens(int n) {List<List<String>…

WGCLOUD使用下发指令重启安卓设备

wgcloud的下发命令很好使&#xff0c;可以下发很多命令&#xff0c;最好的是可以选择很多主机同时下发命令 这里我想重启下我的安卓设备&#xff0c;只需要下发一个命令&#xff1a; bash reboot 就好啦 如下图

PTA 判断两个矩阵相等

Peter得到两个n行m列矩阵&#xff0c;她想知道两个矩阵是否相等&#xff0c;请你用“Yes”&#xff0c;“No”回答她&#xff08;两个矩阵相等指的是两个矩阵对应元素都相等&#xff09;。 输入格式: 第一行输入整数n和m&#xff0c;表示两个矩阵的行与列&#xff0c;用空格隔…

韬光养晦的超绝项目

发展方向 竞技闯关类 可以加入对战系统积累积分&#xff0c;竞技类的接受程度更高&#xff0c;小孩&#xff08;我和我身边大多数人小时候&#xff09;都喜欢玩王者吃鸡这种经济类游戏&#xff0c;开放世界探索&#xff08;本项目、一梦江湖、逆水寒&#xff09;的受众群体年…

最新dofm飞行棋高阶版,分享情侣版飞行棋高级版和终极版

阿星今天要给大家带来一款甜蜜蜜的小游戏——情侣飞行棋。这不是普通的飞行棋&#xff0c;而是专为情侣设计的&#xff0c;让你们的感情在游戏中升温&#xff0c;擦出更多爱的火花。 准备好了吗&#xff1f;跟着阿星一起&#xff0c;咱们来看看这款软件的魅力所在&#xff01;…

Kuberbetes图形化界面之Kuboard

1.1 Kuboard Kuboard 是一个 Kubernetes 可视化管理平台&#xff0c;它提供了一种简单易用的方式来管理和监控 Kubernetes 集群。通过 Kuboard&#xff0c;用户可以轻松地部署、扩展和管理容器化应用程序&#xff0c;同时它还提供了一些高级功能&#xff0c;比如集群监控、日志…

YOLOv10环境搭建、模型预测和ONNX推理

目录 1、开源代码、模型下载 2、环境配置 3、模型预测 4、onnxruntime测试 1、开源代码、模型下载 代码下载链接&#xff1a;https://github.com/THU-MIG/yolov10 模型下载&#xff1a; YOLOv10-N&#xff1a;https://github.com/THU-MIG/yolov10/releases/download/v1.1/…

Linux系统安装AMH服务器管理面板并实现远程访问管理维护

目录 前言 1. Linux 安装AMH 面板 2. 本地访问AMH 面板 3. Linux安装Cpolar 4. 配置AMH面板公网地址 5. 远程访问AMH面板 6. 固定AMH面板公网地址 1. 部署Docker Registry 2. 本地测试推送镜像 3. Linux 安装cpolar 4. 配置Docker Registry公网访问地址 5. 公网远程…

【NumPy】关于numpy.divide()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

Kubernetes可视化界面之DashBoard

1.1 DashBoard Kubernetes Dashboard 是 Kubernetes 集群的一个开箱即用的 Web UI&#xff0c;提供了一种图形化的方式来管理和监视 Kubernetes 集群中的资源。它允许用户直接在浏览器中执行许多常见的 Kubernetes 管理任务&#xff0c;如部署应用、监控应用状态、执行故障排查…

AI整体架构设计6:vLLM

训练大型语言模型以及微调的教程比比皆是&#xff0c;但关于在生产环境中部署它们并监控其活动的资料相对稀缺。上个章节提到了未来云原生的AI是趋势&#xff0c;然而涉及到云原生会比较偏技术。而在此之前为了解决大模型部署量产的问题&#xff0c;社区也一直在探索&#xff0…

数据链路层 + NAT技术

数据链路层&#xff1a;负责设备之间的数据帧的传送和识别。 一、以太网 以太网的帧格式 如何分离报头和有效数据&#xff1f; 报头是固定长度的 如何将数据交给上层协议&#xff1f; 通过类型&#xff0c;如果是0800&#xff0c;则交给IP协议&#xff0c;如果是0806&#xf…

华火硬核专利库丨登创新科技之巅,探创新未至之境

十年的艰苦卓越&#xff0c;“灶”就了华火科技之巅&#xff1b;电生明火的应用&#xff0c;不仅是一次颠覆性的创新&#xff0c;更是对未来厨房的无尽遐想与探索。在当今日新月异的科技时代&#xff0c;创新已成为推动社会进步的重要动力。 华火烹饪科技&#xff0c;以其深厚的…

【空号检测】手机号码状态识别背后的神秘力量:信令检测技术揭秘!

在当今数字化时代&#xff0c;了解一个手机号码的真实状态——是空号、停机还是活跃使用&#xff0c;对于企业运营、客户服务乃至个人通讯管理都至关重要。这一切高效而精准的查询能力&#xff0c;很大程度上归功于一项核心技术——信令检测技术。 免费测试地址&#xff1a;号…

【Django】中间件实现钩子函数预处理和后处理,局部装饰视图函数

在app文件夹里新建middleware.py继承MiddlewareMixin&#xff0c; 编写中间件类&#xff0c;重写process_request、process_response钩子函数 from django.http import HttpRequest, HttpResponse from django.utils.decorators import decorator_from_middleware from django…

使用MyBatis进行批量新增更新操作 ON CONFLICT

1.数据库增加uniques 2.mybatis <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace"co…

蓝桥杯2023(十四届)省赛——接龙数列(DP)

接龙数列&#xff08;DP&#xff09; 1.接龙数列 - 蓝桥云课 (lanqiao.cn) 琢磨半天&#xff0c;本来是开一个三维的&#xff0c;dp[i][j][k] 表示 前i个&#xff0c;以j为首项&#xff0c;k为尾项的最大子集个数&#xff0c;但是实际上用二维即可。想求的是删除个数&#xf…

【SpringCloud】负载均衡

目录 负载均衡什么是负载均衡生活场景为什么需要负载均衡负载均衡手段负载均衡总的来说有两种实现手段负载均衡具体可以通过多种手段来实现 SpringCloud中的负载均衡组件Ribbon VS Nginx负载均衡区别集中式LB进程内LB RibbonRibbon的工作原理Ribbon在工作时分成两步 使用1.提供…

二叉树经典OJ题分析

目录 一、单值二叉树 1.1 题目 1.2 思路 1.3 C语言题解 二、相同的树 2.1 题目 2.2 思路 2.3 C语言题解 三、对称二叉树 3.1 题目 3.2 思路 3.3 C语言题解 四、另一颗树的子树 4.1 题目 4.2 思路 4.3 C语言题解 五、翻转二叉树 5.1 题目 5.2 思路 5.3 C语言…