当前位置: 首页 > news >正文

【JavaEE】(25) Spring 原理

一、Bean 的作用域

        Bean 的作用域指 Bean 的某种行为模式

1、单例作用域(singleton,默认)

        在整个 Spring IoC 容器内,同名 bean 只有一个。无论用什么方式获取同名 Bean,都是一样的,并且某一地方修改了 Bean,其它地方使用的也是修改后的值。

        存储 Bean 时,若未指定作用域默认为单例作用域

// 类注解,存储 Bean
@Configuration
public class BeanConfig {
}
@RestController
@RequestMapping("/scope")
public class ScopeController {@Autowiredprivate ApplicationContext context;// 默认情况,单例作用域@Autowiredprivate BeanConfig beanConfig;// 测试默认作用域@RequestMapping("test")public String test() {return "context bean: " + context.getBean(BeanConfig.class) + "<br/>" +"config bean: " + beanConfig;}
}

        也可以指定单例作用域

public class Dog {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
// 未指定作用域,默认单例作用域
@Configuration
public class BeanConfig {// 指定作用域为 singleton@Bean
//    @Scope("singleton")@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)public Dog singleton() {return new Dog();}
}
    // singleton 作用域@Autowiredprivate Dog singleton;// 测试单例作用域@RequestMapping("testSingleton")public String testSingleton() {return "context bean: " + context.getBean("singleton") + "<br/>" +"config bean: " + singleton;}

 

        重启后,Spring IoC 容器销毁,bean 也销毁了。

2、原型作用域(prototype,多例)

        每次使用同名 bean,都会新建实例

    // 指定作用域为 prototype@Bean//    @Scope("prototype")@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public Dog prototype() {return new Dog();}
    // prototype 作用域@Autowiredprivate Dog prototype;// 测试原型作用域@RequestMapping("testPrototype")public String testPrototype() {return "context bean: " + context.getBean("prototype") + "<br/>" +"config bean: " + prototype;}

        下面不变的原因:@AutiWired 

3、请求作用域(request)

        每次 HTTP 请求内的同名 bean 相同。 

    // 指定作用域为 request@Bean@RequestScopepublic Dog request() {return new Dog();}
    // request 作用域@Autowiredprivate Dog request;// 测试请求作用域@RequestMapping("testRequest")public String testRequest() {return "context bean: " + context.getBean("request") + "<br/>" +"config bean: " + request;}

       每次请求,会新建 bean。

4、会话作用域(session)

        每个 HTTP 会话中,同名 bean 相同

    // 指定作用域为 session@Bean@SessionScopepublic Dog session() {return new Dog();}
    // session 作用域@Autowiredprivate Dog session;// 测试 session 作用域@RequestMapping("testSession")public String testSession() {return "context bean: " + context.getBean("session") + "<br/>" +"config bean: " + session;}

        不同浏览器,会话不同,新建 bean。

5、全局作用域(application)

        ServletContext 内同名 bean 相同ServletContext 可以认为是一个 Tomcat 容器,一个 Tomcat 可包含多个 ApplicationContext。实际开发中几乎没用,因为 Spring Boot 项目中,一个 Tomcat 只能包含一个 ApplicationContext;并且 ApplicationContext 在业务上也是隔离的,不可能这个 ApplicationContext 的 bean 放到另一个 ApplicationContext 里面用。

    // 指定作用域为 application@Bean@ApplicationScopepublic Dog application() {return new Dog();}
    // application 作用域@Autowiredprivate Dog application;// 测试全局作用域@RequestMapping("testApplication")public String testApplication() {return "context bean: " + context.getBean("application") + "<br/>" +"config bean: " + application;}

6、WebSocket 作用域

        每个 WebSocket 内的同名 bean 相同。

二、Bean 的生命周期

1、概念

  • 实例化:给 bean 分配内存空间。
  • 属性赋值:注入属性里的 bean,如 @AutoWired。
  • 初始化:a. 执行各种通知,如 BeanNameAware 接口。b. 执行初始化:初始化的自定义扩展内容实现:(注解方式) @PostConstruct;(xml 方式) init-method;(实现接口方式) BeanPostProcessor。
  • 使用
  • 销毁:销毁的自定义扩展内容实现:(注解方式) @PreDestroy;(xml 方式) destroy-method;(实现接口方式) DisposableBean。

        执行顺序图示:

2、代码演示

// 将 BeanLifeComponent 注册为 Spring Bean
@Component
public class BeanLifeComponent implements BeanNameAware {private BeanConfig beanConfig;// 1. 实例化public BeanLifeComponent() {System.out.println("执行构造函数...");}// 2. 属性赋值@Autowiredpublic void setBeanConfig(BeanConfig beanConfig) {this.beanConfig = beanConfig;System.out.println("执行 setter 方法...");}// 3. 初始化 a. 执行各种通知@Overridepublic void setBeanName(String s) {System.out.println("执行 BeanNameAware 接口的 setBeanName 方法,beanName = " + s);}// 4. 初始化 b. 执行自定义初始化方法(注解方式)@PostConstructpublic void init() {System.out.println("执行自定义初始化方法...");}// 5. 使用 beanpublic void use() {System.out.println("执行 user 方法...");}// 6. 自定义销毁方法(注解方式)@PreDestroypublic void destroy() {System.out.println("执行自定义销毁方法...");}
}

        在测试中使用,能演示销毁:

    @Testvoid contextLoads() {BeanLifeComponent bean = (BeanLifeComponent) context.getBean("beanLifeComponent");bean.use();}

3、源码阅读

        查找 AbstractAutowireCapableBeanFactory 类的 doCreateBean 方法。

(1)实例化

        doCreateBean:

        createBeanInstance:

(2)属性赋值

        doCreateBean:

        populateBean(填充 bean):

(3)初始化

        doCreateBean:

        initializeBean:

        逐个分析:

        ① invokeAwareMethouds:

        ② applyBeanPostProcessorsAfterInitialization:

        ③ invokeInitMethods:

(4)销毁

        doCreateBean:把 bean 的销毁方法注册到 Spring。        

三、Spring Boot 自动配置

        Spring 自动装配指的是 DI(依赖注入)。而 Spring 自动配置指的是,Spring 项目启动后,第三方 jar 包里的一些类、bean 对象自动存入Srping 容器,无需使用 jar 包者手动声明存入。

1、Spring 管理第三方配置类(问题重现)

        配置类:指的是第三方包希望让 Spring 管理的类,而不是所有类。

        模拟第三方 jar 包:

        Config1:2、3 类似

package com.edu.spring.config;import org.springframework.context.annotation.Configuration;@Configuration
public class Config1 {
}

        由于启动类在我们的项目中,所以扫描不到第三方的类,Spring 也就无法管理它的 bean:

    // 测试获取第三方 bean@Testvoid testConfig() {Config1 config1 = context.getBean(Config1.class);System.out.println(config1);}

(1)方法1(@Component)

        手动声明需要扫描的类所在路径:

        但这种方式不好。一是麻烦,需要一个个 jar 包声明路径。二是有时不希望让 jar 包中的所有类的 bean 让 Spring 管理(jar 包使用者可能不会用到所有功能,所以有些类所需的依赖,pom 文件中根本没有引入,让 Spring 管理反而有问题。比如我们模拟的第三方,希望 Config1、Config2 被管理,不希望 Config3),@Component 就无法做到这一点。

(2)方法2(@Import)

        将希望被 Spring 管理的类一个个列到 @Import 中:(可以做到让 jar 包部分类被管理)

        但这种方式仍然很麻烦。并且使用者不清楚哪些类希望被管理,只有 jar 包开发者最清楚。

(3)方法3(第三方提供注解,Spring Boot 采用)

        这种注解常以 @Enable 开头。第三方提供的注解,里面列出了需要被管理的类,使用者只需加上该注解即可,类似于一些中间件,会提供 @Enablexxx 注解开启第三方服务。

        实现 ImportSelector 接口,重写方法指定需要被管理的类:

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.edu.spring.config.Config1","com.edu.spring.config.Config2"};}
}

       @Enablexxx 注解定义:

        使用者加入注解,即可让第三方的 bean 让 Spring 管理:

        但这还不够好,因为每加入一个第三方中间件,都要手动加注解,不够方便

(4)方法4(约定配置类文件路径,Spring Boot 采用)

        Spring Boot 3.0.2 约定的自动配置类集合路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,创建该路径:

        列出需要被自动配置的类:

        第三方包举例:pom 文件引入 mybatis 依赖,Spring Boot 会自动扫描 MyBatis 约定自动配置类文件的内容:

2、源码阅读

        启动类上的 @SpringBootApplication 注解:

        深入 @EnableAutoConfiguration:

(1)@Import({AutoConfigurationImportSelector.class})

        导入了 AutoConfigurationImportSelector.class,它实现了 ImportSelector 接口的 selectImports 方法:

        深入阅读 getAutoConfigurationEntry():debug,观察结果。

        深入阅读 getCandidateConfigurations(annotationMetadata, attributes):文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

        查看自动配置类文件:我的是 Spring Boot 3.0.2 版本,这里面存放的是原生的+集成的第三方的。

        这些配置类需要排除掉用户未使用的,比如 Rabbit,我们在 POM 中没有引入该依赖,所有 Rabbit 源码中导入不了相关类,导致很多报红错误:

        查看一个我们之前了解的 JDBC 事务源码:

        源码使用的 @Conditional 有条件地将 bean 交给 Spring 管理:(未达到条件的自动配置类、bean 就会被排除掉)因为我们的 pom 文件没有引入 JDBC 事务的依赖,所以找不到指定类,也就不会自动配置 DataSourceTransaction.... 类。 这也就是排除的原理。

        一些常见的 @Conditional:

(2)@AutoConfigurationPackage

        深入阅读 @AutoConfigurationPackage:                

        AutoConfigurationPackages.Registrar.class:

        如 MyBatis 实现了 ImportBeanDefinitionRegistrar 接口的 registerBeanDefinitions 方法,就可以扫描我们的项目中被 @Mapper 声明的 bean:

3、总结

        Spring Boot 自动配置原理流程:


文章转载自:

http://nvFH20CY.hzryL.cn
http://U2tGdJVB.hzryL.cn
http://l2GYXBhM.hzryL.cn
http://BjHAAYoe.hzryL.cn
http://UvU5xUIS.hzryL.cn
http://kN3HYWvN.hzryL.cn
http://10QEb9Bq.hzryL.cn
http://20wkNj78.hzryL.cn
http://J6DGdHyl.hzryL.cn
http://ESaEXxTR.hzryL.cn
http://agT8qbMv.hzryL.cn
http://yOGEYTwV.hzryL.cn
http://3CJK9Uxn.hzryL.cn
http://AVeCuPOH.hzryL.cn
http://1CG1OHse.hzryL.cn
http://XtKs9Rln.hzryL.cn
http://7eUXMgQ3.hzryL.cn
http://BCgd08ij.hzryL.cn
http://D3wxOhG1.hzryL.cn
http://LPA6V6O9.hzryL.cn
http://dBOXrC5d.hzryL.cn
http://GsAbpjNG.hzryL.cn
http://DKgoF9mI.hzryL.cn
http://20mvDvQ4.hzryL.cn
http://QjgrCNAH.hzryL.cn
http://qdluvmWS.hzryL.cn
http://odZXIZVo.hzryL.cn
http://9rOs0Iwo.hzryL.cn
http://4OdFHu2Q.hzryL.cn
http://Qi8kdOCg.hzryL.cn
http://www.dtcms.com/a/378551.html

相关文章:

  • 【科研绘图系列】R语言绘制模型预测与数据可视化
  • 音频中的PDM、PCM概念解读
  • 离线应用开发:Service Worker 与缓存
  • 1、RocketMQ概念详解
  • ZooKeeper Multi-op+乐观锁实战优化:提升分布式Worker节点状态一致性
  • 使用yolo算法对视频进行实时目标跟踪和分割
  • Tomcat日志乱码了怎么处理?
  • 新手该选哪款软件?3ds Max vs Blender深度对比
  • 剧本杀小程序系统开发:构建线上线下融合的剧本杀生态圈
  • 常用加密算法之 AES 简介及应用
  • 【SQL注入系列】JSON注入
  • 盲盒抽卡机小程序:从0到1的蜕变之路
  • 设计模式(C++)详解—工厂方法模式(1)
  • 【Proteus仿真】【51单片机】教室灯光控制器设计
  • java语言中,list<String>转成字符串,逗号分割;List<Integer>转字符串,逗号分割
  • Jenkins运维之路(Jenkins流水线改造Day01)
  • 9月11日星期四今日早报简报微语报早读
  • 阿里兵临城下,美团迎来至暗时刻?
  • 学习笔记:Javascript(5)——事件监听(用户交互)
  • window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
  • [K8S学习笔记] Service和Ingress的关系
  • YOLO11实战 第018期-基于yolo11的水果甜瓜目标检测实战文档(详细教程)
  • 【已解决】mongoose在mongodb中添加数据,数据库默认复数问题
  • 借助自动化GPO报表增强AD域安全性
  • decentralized英文单词学习
  • 响应式布局
  • Vue基础知识-Vue集成 Element UI全量引入与按需引入
  • 《UE5_C++多人TPS完整教程》学习笔记52 ——《P53 FABRIK 算法(FABRIK IK)》
  • 网络编程套接字(UDP)
  • Git子模块(Submodule)合并冲突的原理与解决方案