Spring 原理

1. Bean的作用域

在SpringloC&DI阶段,我们学习了Spring是如何帮助我们管理对象的.

  1. 通过@Controller,@Service,@Repository,@Component,@Configuration,@Bean来声明Bean对象.
  2. 通过ApplicationContext或者BeanFactory来获取对象
  3. 通过@Autowired,Setter方法或者构造方法等来为应用程序注入所依赖的Bean对象

我们来简单回顾一下

1.通过@Bean声明 bean,把bean存在Spring容器中

  1. 从Spring 容器中获取Bean

也可以通过在代码中直接注入ApplicationContext的方式来获取Spring容器

修改代码,从Spring中多次获取Bean

观察运行结果:

发现输出的bean对象地址值是一样的,说明每次从Spring容器中取出来的对象都是同一个。

  • 这也是"单例模式"
  • 单例模式:确保一个类只有一个实例,多次创建也不会创建出多个实例

默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域 。

Bean的作用域是指Bean在Spring框架中的某种行为模式。

比如单例作用域:表示Bean在整个Spring中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另一个人读取到的就是被修改的值。

修改上述代码,给Dog添加属性name

修改测试代码:

观察运行结果:

那么能不能将bean对象设置为非单例的(每次获取的bean都是一个新对象呢)?

这就是Bean的不同作用域了。

① Bean的作用域

在Spring中支持6种作用域,后4种在SpringMVC环境才生效

  1. singleton:单例作用域
  2. prototype:原型作用域(多例作用域)
  3. request:请求作用域
  4. session:会话作用域
  5. Application: 全局作用域
  6. websocket: HTTP WebSocket 作用域

这里介绍一下 @Scope注解,@Scope 注解的核心作用是定义 Bean 实例的生命周期和可见范围,即控制对象的创建、复用和销毁规则。

在 Spring 框架中:定义 Bean 作用域,且@Scope 用于指定 @Component 或 @Bean 所定义对象的作用域。

主要作用域类型:

作用域 说明
singleton 每个 Spring IoC 容器内同名称的 bean 只有一个实例 (单例)(默认)
prototype 每次使用该 bean 时( getBean() ) 都会创建新的实例 (多例)
request 每个 HTTP 请求生命周期内,创建新的实例 (web 环境中)
session 每个 HTTP Session 生命周期内,创建新的实例 (web 环境中)
application 每个 ServletContext 生命周期内,创建新的实例 (web 环境中)
websocket 每个 WebSocket 生命周期内,创建新的实例 (web 环境中)

通过代码开观察Bean的作用域

1. 单例作用域

访问:http://127.0.0.1:8080/dog/singleton

多次访问,得到的都是同一个对象,并且 @Resource 和 context.getBean() 也是同一个对象。

2. 多例作用域

访问:http://127.0.0.1:8080/dog/prototype

观察ContextDog, 每次获取的对象都不⼀样,而注入的对象在Spring容器启动时, 就已经注了, 所以多次请求也不会发生变化。

3. 请求作用域

@RequestScope 注解等于 @Scope("request") 注解:

访问:http://127.0.0.1:8080/dog/request

在一次请求中,@Resource 和 context.getBean()也是同一个对象,但是每次请求,都会重新创建对象。

4. 会话作用域

访问:http://127.0.0.1:8080/dog/session

在⼀个session中, 多次请求, 获取到的对象都是同⼀个

换⼀个浏览器访问, 发现会重新创建对象.(另⼀个Session)

5. Application 作用域

访问:http://127.0.0.1:8080/dog/application

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

换一个浏览器也是一样

Application scope就是对于整个web容器来说,bean的作用域是ServletContext级别的。

这个和singleton有点类似,区别在于: Applicationscope是ServletContext的单例,singleton是一个ApplicationContext 的单例 。在一个web容器中ApplicationContext可以有多个。


2. Bean的生命周期

生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期。

Bean的生命周期分为以下5个部分:

  • 1.实例化(为Bean分配内存空间)
  • 2.属性赋值(Bean注入和装配,比如@Autowired)
  • 3.初始化
    • a.执行各种通知,如BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法.
    • b.执行初始化方法
      • xml定义init-method
      • 使用注解的方式@PostConstruct
      • 执行初始化后置方法(BeanPostProcessor)
    1. 使用Bean
  • 5.销毁Bean
    • a.销毁容器的各种方法,如@PreDestroy,DisposableBean接口方法,destroy-method.

实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段,可以在实例化之后,类加载完成之前进行自定义"事件"处理。

比如我们现在需要买一栋房子,那么我们的流程是这样的:

1.先买房(实例化,从无到有)

2.装修(设置属性)

3.买家电,如洗衣机,冰箱,电视,空调等(各种初始化,可以入住);

4.入住(使用Bean)

5.卖房(Bean销毁)


通过代码演示:

测试:

执行结果:

通过运行结果观察 1. 先执行构造函数 2. 设置属性 3. Bean初始化 4. 使用Bean 5. 销毁Bean

3. Spring Boot 自动配置

SpringBoot的自动配置就是当Spring容器启动后,一些配置类,bean对象等就自动存入到了loC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

SpringBoot自动配置,就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到SpringloC容器中的。

我们学习主要分以下两个方面:

1.Spring是如何把对象加载到SpringloC容器中的

2.SpringBoot 是如何实现的

① Spring 加载Bean

**需求:**使用Spring管理第三方的jar包的配置

  • 引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入

数据准备:

1.创建项目spring-autoconfig,当前项目目录为com.example.principle

2.模拟第三方代码文件在com.example.autoconfig目录下

第三方文件代码:

  1. 获取 UserConfig 这个Bean

运行程序:

观察日志: No qualifying bean of type 'com.bite.autoconfig.BiteConfig' available

没有com.example.autoconfig.UserConfig这个类型的Bean

② 原因分析

Spring通过五大注解和@Bean注解可以帮助我们把Bean加载到SpringloC容器中,以上有个前提就是这些注解类需要和SpringBoot启动类在同一个目录下 (@SpringBootApplication标注的类就

是SpringBoot项目的启动类).

启动类所在目录为:com.example.principle,而UserConfig这个类在com.example.autoconfig下,所以SpringBoot并没有扫描到。

当我们引入第三方的Jar包时,第三方的Jar代码目录肯定不在启动类的目录下,如何告诉Spring帮我们管理这些Bean呢?

③ 解决方案

我们需要指定路径或者引入的文件, 告诉Spring, 让Spring进行扫描到。

1. @ComponentScan 组件扫描

通过 @ComponentScan 注解, 指定Spring扫描路径

也可以指定扫描多个包: 例如:@ComponentScan({"com.example.autoconfig","com.example.principle"})

运行结果:

可以看到,这次UserConfig Bean获取到了

Spring是否使用了这种方式呢?

非常明显,没有,(因为我们引入第三方框架时,没有加扫描路径。比如mybatis)

如果SpringBoot采用这种方式,当我们引|入大量的第三方依赖,比如Mybatis,jackson等时,就要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。

2. @Import 导入

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

@Import 导入主要有以下几种形式:

  1. 导入类
  2. 导入ImportSelector接口实现类

导入类

运行程序:

问题:如果又有多了一些配置项呢?

可以采用导入多个类:

很明显, 这种方式也很繁琐。 所以, SpringBoot依然没有采用。

2. 导入ImportSelector接口实现类

ImportSelector 接口实现类:

启动类:

运行程序:

可以看到, 我们采用这种方式也可以导入第三方依赖提供的Bean。

问题:

但是他们都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些Bean对象或配置类,如果漏掉其中一些Bean,很可能导致我们的项目出现大的事故。

这对程序员来说非常不友好。

依赖中有哪些Bean,使用时需要配置哪些bean,第三方依赖最清楚,那能否由第三方依赖来做这件事呢?

  • 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以**@EnableXxxx开头** 的注解,注解中封装的就是@Import注解

1.第三方依赖提供注解

注解中封装 @Import 注解, 导入 MySelector.class

  1. 在启动类上使用第三方提供的注解

运行程序:

可以看到,这种方式也可以导入第三方依赖提供的Bean。

并且这种方式更优雅一点.SpringBoot采用的也是这种方式。

4. Spring原理分析

① 源码阅读

SpringBoot 是如何帮助我们做的呢? ⼀切的来自起源SpringBoot的启动类开始 @SpringBootApplication 标注的类 就是SpringBoot项目的启动类

@SpringBootApplication 注解是SpringBoot实现自动配置的核心。

@SpringBootApplication是一个组合注解,注解中包含了:

1.元注解

JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为meta-annotation(元注

解),他们分别是:

  • @Target描述注解的使用范围(即被修饰的注解可以用在什么地方)
  • @Retention描述注解保留的时间范围
  • @Documented描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息
  • @Inherited使被它修饰的注解具有继承性(如果某个类使用了被@lnherited修饰的注解,则其子类将自动具有该注解)

2. @SpringBootConfiguration

里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已。 (@Indexed注解,是用来加速应用启动的,不用关心)

3.@EnableAutoConfiguration(开启自动配置)

Spring自动配置的核心注解,下面详细讲解。

4.@ComponentScan(包扫描)

可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包,如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也是为什么SpringBoot项自声明的注解类必须要在启动类的目录下。

(1) @EnableAutoConfiguration 详解

看下 @EnableAutoConfiguration 注解的实现:

这个注解包含两部分:

1. @Import({[AutoConfigurationlmportSelector.class})

使用@lmport注解,导入了实现ImportSelector接口的实现类.

selectImports() 方法底层调用 getAutoConfigurationEntry() 方法, 获取可自动配置的配置类信息集合。

点进去:

getAutoConfigurationEntry()方法通过调用getCandidateConfigurations(annotationMetadata,attributes)方法获取在配置文件中配置的所有自动配置类的集合。

点进去:

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
        List<String> configurations = importCandidates.getCandidates();
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

getCandidateConfigurations方法的功能:获取所有基于

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,META-INF/spring.factories文件中配置类的集合.

在引入的起步依赖中,通常都有包含以上两个文件

这里面包含了很多第三方依赖的配置文件(连续按两下shift可以查看对应的源码)

  • 在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。
    • @Conditional是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
  • META-INF/spring.factories文件是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件中配置即可,Spring就会把相应的实现类注入到Spring容器中。
    • 注:会加载所有jar包下的classpath路径下的META-INF/spring.factories文件,这样文件不止一个.

示例,我们加入mybatis-plus 和 MySQL驱动的依赖:

XML 复制代码
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
	<version>3.5.5</version>
</dependency>
<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>

然后就会引入第三方配置文件:

2. @AutoConfigurationPackage

源码如下:

这个注解主要是导⼊⼀个配置文件 AutoConfigurationPackages.Registrar.class

Registrar实现了ImportBeanDefinitionRegistrar类,就可以被注解@lmport导入到spring容器里。

(String\[\]) (new PackageImports(metadata)) .getPackageNames() .toArray(newStringo):当前启动类所在的包名.

所以:@AutoConfigurationPackage就是将启动类所在的包下面所有的组件都扫描注册到spring 容器中.

相关推荐
温暖小土1 小时前
Spring AI 接入 DeepSeek Chat 模型
数据库·人工智能·spring
weixin_537590452 小时前
《Spring快速入门到精通》- 实例1.1
java·spring·intellij-idea
曹牧2 小时前
Spring MVC:@RequestMapping
java·spring·mvc
卓怡学长3 小时前
w203基于springboot钢材仓库管理系统的设计与实现
java·spring boot·spring·intellij-idea
卓怡学长4 小时前
w213基于Spring Boot框架的Web安全学习系统的设计与实现
java·spring boot·spring·web安全·intellij-idea
霸道流氓气质4 小时前
Spring AI Alibaba Graph Studio 入门指南:可视化Agent编排与调试平台
java·人工智能·spring
Roadinforest5 小时前
Spring Boot 4 六步入门实录:从 Hello World 到一个能交付的 Todo API
java·开发语言·spring boot·spring
paopaokaka_luck5 小时前
个人综合管理小程序(echarts数据看板、健康数据记录与趋势查看、目标拆解和进度管理、日程待办与番茄钟、习惯打卡、收支统计、个人生活记录、跨模块智能提醒)
前端·javascript·spring·小程序·数据分析·echarts·生活
牧瀬クリスだ5 小时前
Mybatis-Plus:让CRUD快如闪电
java·spring boot·spring·tomcat·mybatis·yml