Spring源码-4.Aware接口、初始化和销毁执行顺序、Scope域

Aware接口

其实在生命周期中,Aware接口也参与进来了,如图所示:

在这里插入图片描述

如初始化时的第三步,其实就是调用了Aware相关接口。

以常见的Aware接口举例:

1.BeanNameAware 主要是注入Bean的名字

2.BeanFactoryAware 主要是时注入BeanFactory容器

3.ApplicationContextAware 主要是注入ApplicationContext容器

接下来以一段代码的方式来解析吧。

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myBean", MyBean.class);public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {private static final Logger log = LoggerFactory.getLogger(MyBean.class);@Overridepublic void setBeanName(String name) {// 初始化之前回调 BeanNameAware接口log.debug("当前bean " + this + " 名字叫:" + name);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.debug("当前bean " + this + " 容器是:" + applicationContext);}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("当前bean " + this + " 初始化");}
}context.refresh(); 
context.close();

输出:

[DEBUG] 11:36:41.083 [main] com.itheima.a06.MyBean              - 当前bean com.itheima.a06.MyBean@130161f7 名字叫:myBean 
[DEBUG] 11:36:41.102 [main] com.itheima.a06.MyBean              - 当前bean com.itheima.a06.MyBean@130161f7 容器是:org.springframework.context.support.GenericApplicationContext@de3a06f, started on Tue Oct 24 11:36:41 CST 2023 
[DEBUG] 11:36:41.103 [main] com.itheima.a06.MyBean              - 当前bean com.itheima.a06.MyBean@130161f7 初始化 

不同于我们前面章节所介绍的后置处理,我们不需要添加任何的后置处理器,只需要实现对应的Aware接口,在运行的时候,就会执行对应的实现方法了。

以上就是Aware的初步认识了,那下面我们再来看,后置处理器的使用

context.registerBean("myConfig1", MyConfig1.class);
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.registerBean(CommonAnnotationBeanPostProcessor.class);@Configuration
public class MyConfig1 {private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext) {log.debug("注入 ApplicationContext");}@PostConstructpublic void init() {log.debug("初始化");}
}

输出:

[DEBUG] 11:41:48.451 [main] com.itheima.a06.MyConfig1           - 注入 ApplicationContext 
[DEBUG] 11:41:48.456 [main] com.itheima.a06.MyConfig1           - 初始化 

其实这里就和之前介绍的一样了,因为加入了对应的后置处理器,就能解析到 @Autowired 和 @PostConstruct注解了。

其实到这里来说,可以理解为Aware接口其实 和 后置处理器很像很像,都是能干预到生命周期的,在生命周期图中也能够很清晰的看到。

但是还是有本质的去别的,简单地说:

  • @Autowired 的解析需要用到 bean 后处理器, 属于扩展功能
  • 而 Aware 接口属于内置功能, 不加任何扩展, Spring 就能识别

其实这样说就非常清晰了,但是还有有一点很大的不同就是内置的注入和初始化不受扩展功能的影响,总会被执行,而扩展功能受某些情况影响可能会失效。

还是在Myconfig1中添加一个bean

@Bean //  beanFactory 后处理器public BeanFactoryPostProcessor processor1() {return beanFactory -> {log.debug("执行 processor1");};}

此时再运行,发现,并没有执行@Autowired 和 @PostConstruct

[INFO ] 11:45:21.941 [main] o.s.c.a.ConfigurationClassEnhancer  - @Bean method MyConfig1.processor1 is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details. 
[DEBUG] 11:45:21.952 [main] com.itheima.a06.MyConfig1           - 执行 processor1 

这是什么原因导致了@Autowired失效呢?

其实 Context.refresh()方法中,是有一个默认的初始化顺序

1.beanfactory后处理器

2.bean后处理器

3.初始化单例

以一张图来形容一下:

在这里插入图片描述

Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效

在这里插入图片描述

根据以上分析的再给出一段代码举例:

@Configuration
public class MyConfig2 implements InitializingBean, ApplicationContextAware {private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);@Overridepublic void afterPropertiesSet() throws Exception {log.debug("初始化");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.debug("注入 ApplicationContext");}@PostConstructpublic void init() {log.debug("初始化");}@Bean //  beanFactory 后处理器public BeanFactoryPostProcessor processor2() {return beanFactory -> {log.debug("执行 processor2");};}
}

输出:

[DEBUG] 11:51:22.230 [main] com.itheima.a06.MyConfig2           - 注入 ApplicationContext 
[DEBUG] 11:51:22.235 [main] com.itheima.a06.MyConfig2           - 初始化 
[INFO ] 11:51:22.237 [main] o.s.c.a.ConfigurationClassEnhancer  - @Bean method MyConfig2.processor2 is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details. 
[DEBUG] 11:51:22.245 [main] com.itheima.a06.MyConfig2           - 执行 processor2 

由此可以分析总结:

  • Aware 接口提供了一种【内置】 的注入手段, 可以注入 BeanFactory, ApplicationContext
  • InitializingBean 接口提供了一种【内置】的初始化手段
  • 内置的注入和初始化不受扩展功能的影响, 总会被执行, 因此 Spring 框架内部的类常用它们

初始化和销毁的执行顺序

想要验证初始化 和 销毁的执行顺序,最直接的办法其实就是打印出来,其实Aware接口我们已经很详细的介绍了后置处理器与Aware接口的执行顺序了,在此处,再加上@Bean指定的初始化方法进行综合对比:

ConfigurableApplicationContext context = SpringApplication.run(A.class, args);
context.close();@Bean(initMethod = "init3")public Bean1 bean1() {return new Bean1();}@Bean(destroyMethod = "destroy3")public Bean2 bean2() {return new Bean2();}public class Bean1 implements InitializingBean {private static final Logger log = LoggerFactory.getLogger(Bean1.class);// 扩展@PostConstructpublic void init1() {log.debug("初始化1");}// 内置@Overridepublic void afterPropertiesSet() throws Exception {log.debug("初始化2");}public void init3() {log.debug("初始化3");}
}public class Bean2 implements DisposableBean {private static final Logger log = LoggerFactory.getLogger(Bean2.class);@PreDestroypublic void destroy1() {log.debug("销毁1");}@Overridepublic void destroy() throws Exception {log.debug("销毁2");}public void destroy3() {log.debug("销毁3");}
}

输出:

[DEBUG] 14:52:57.353 [main] com.itheima.a07.Bean1               - 初始化1 
[DEBUG] 14:52:57.353 [main] com.itheima.a07.Bean1               - 初始化2 
[DEBUG] 14:52:57.354 [main] com.itheima.a07.Bean1               - 初始化3 ......[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2               - 销毁1 
[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2               - 销毁2 
[DEBUG] 14:52:57.758 [main] com.itheima.a07.Bean2               - 销毁3 

其初始化顺序为:

  1. @PostConstruct 标注的初始化方法
  2. InitializingBean 接口的初始化方法
  3. @Bean(initMethod) 指定的初始化方法

销毁顺序为:

  1. @PreDestroy 标注的销毁方法
  2. DisposableBean 接口的销毁方法
  3. @Bean(destroyMethod) 指定的销毁方法

Scope

在当前版本的 Spring 和 Spring Boot 程序中,scope范围共有五个,singleton, prototype, request, session, application(globalSession已经废弃了)

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

其中 前两个为最常见的scope,不多做赘述,主要是介绍后三个,以一个Springboot长须举例

SpringApplication.run(A.class, args);@Scope("request")
@Component
public class BeanForRequest {private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);@PreDestroypublic void destroy() {log.debug("destroy");}}@Scope("session")
@Component
public class BeanForSession {private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);@PreDestroypublic void destroy() {log.debug("destroy");}
}@Scope("application")
@Component
public class BeanForApplication {private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);@PreDestroypublic void destroy() {log.debug("destroy");}
}@RestController
public class MyController {@Lazy@Autowiredprivate BeanForRequest beanForRequest;@Lazy@Autowiredprivate BeanForSession beanForSession;@Lazy@Autowiredprivate BeanForApplication beanForApplication;@GetMapping(value = "/test", produces = "text/html")public String test(HttpServletRequest request, HttpSession session) {ServletContext sc = request.getServletContext();String sb = "<ul>" +"<li>" + "request scope:" + beanForRequest + "</li>" +"<li>" + "session scope:" + beanForSession + "</li>" +"<li>" + "application scope:" + beanForApplication + "</li>" +"</ul>";return sb;}}

启动springboot,访问http://localhost:8080/test发现:

在这里插入图片描述

对于一个请求来说,会涉及到这三个作用域,当重新刷新页面的时候

在这里插入图片描述

可以发现对应的BeanForRequest是变化的了,这就是request的作用域,随着每次请求而变化。

同理,对应的session对应的是一个会话,在这里可以修改会话的时间或者重新启动一个新的浏览器再次访问

在这里插入图片描述

发现对应的BeanForSession也随着发生了变化。

以上就是scope域的作用范围,但是细心的同学其实能发现,在Controller中,由于Spring的Bean默认是单例的,而我们@Autowired都不是单例,甚至随着作用域的变化而变化,都分别加了@Lazy 注解,那么这个注解的作用是什么呢?

将注解去掉重新测试,发现,无论我们怎么刷新,request 和 session都不会再变化了,这是为什么呢?

其实这就是一个典型的singleton注入其它scope失效的问题。

以单例注入多例为例

有一个单例对象E

@Component
public class E {private static final Logger log = LoggerFactory.getLogger(E.class);private F f;public E() {log.info("E()");}@Autowiredpublic void setF(F f) {this.f = f;log.info("setF(F f) {}", f.getClass());}public F getF() {return f;}
}

要注入的对象 F 期望是多例

@Component
@Scope("prototype")
public class F {private static final Logger log = LoggerFactory.getLogger(F.class);public F() {log.info("F()");}
}

测试

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

输出

com.itheima.demo.cycle.F@6622fc65
com.itheima.demo.cycle.F@6622fc65

发现它们是同一个对象,而不是期望的多例对象

对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F

在这里插入图片描述

解决:

使用@Lazy生成代理

代理对象虽然还是同一个,但当每次使用代理对象的任意方法的时候,由代理创建新的f对象

在这里插入图片描述

所以就能很有效的解决singleton注入 其他scope的问题啦。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue项目中将html转为pdf并下载

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人…

深度学习:激活函数曲线总结

深度学习&#xff1a;激活函数曲线总结 在深度学习中有很多时候需要利用激活函数进行非线性处理&#xff0c;在搭建网路的时候也是非常重要的&#xff0c;为了更好的理解不同的激活函数的区别和差异&#xff0c;在这里做一个简单的总结&#xff0c;在pytorch中常用的激活函数的…

Spring Cloud Sentinel整合Nacos实现配置持久化

sentinel配置相关配置后无法持久化&#xff0c;服务重启之后就没了&#xff0c;所以整合nacos&#xff0c;在nacos服务持久化&#xff0c;sentinel实时与nacos通信获取相关配置。 使用上一章节Feign消费者服务实现整合。 版本信息&#xff1a; nacos:1.4.1 Sentinel 控制台 …

基于springboot实现CSGO赛事管理系统【项目源码+论文说明】

基于SpringBoot实现CSGO赛事管理系统演示 摘要 CSGO赛事管理系统是针对CSGO赛事管理方面必不可少的一个部分。在CSGO赛事管理的整个过程中&#xff0c;CSGO赛事管理系统担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类的管理系统也在不断改进。本课题所设计…

什么是Spring Web MVC

Spring Web MVC 概念 Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中。它的 正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC". 什么是Servlet Servlet 是…

回流重绘零负担,网页加载快如闪电

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、回…

rust 创建多线程web server

创建一个 http server&#xff0c;处理 http 请求。 创建一个单线程的 web 服务 web server 中主要的两个协议是 http 和 tcp。tcp 是底层协议&#xff0c;http 是构建在 tcp 之上的。 通过std::net库创建一个 tcp 连接的监听对象&#xff0c;监听地址为127.0.0.1:8080. us…

温湿度计传感器DHT11控制数码管显示verilog代码及视频

名称&#xff1a;温湿度计传感器DHT11控制数码管显示 软件&#xff1a;QuartusII 语言&#xff1a;Verilog 代码功能&#xff1a; 使用温湿度传感器DHT11采集环境的温度和湿度&#xff0c;并在数码管显示 本代码已在开发板验证 开发板资料&#xff1a; 大西瓜第一代FPGA升级…

Vue--》简易资金管理系统后台项目实战(前端)

今天开始使用 vue3 + ts + node 搭建一个简易资金管理系统的前后端分离项目,因为前后端分离所以会分两个专栏分别讲解前端与后端的实现,后端项目文章讲解可参考:后端链接,我会在前后端的两类专栏的最后一篇文章中会将项目代码开源到我的github上,大家可以自行去进行下载运…

Notepad++安装插件和配置快捷键

Notepad是一款轻量级、开源的文件编辑工具&#xff0c;可以编辑、浏览文本文件、二进制文件、.cpp、.java、*.cs等文件。Notepad每隔1个月&#xff0c;就有一个新版本&#xff0c;其官网是&#xff1a; https://github.com/notepad-plus-plus/notepad-plus-plus。这里介绍其插件…

系列十四、Spring如何处理线程安全问题

一、线程安全问题出现的原因 Spring中出现线程安全的原因是&#xff0c;单实例bean中存在成员变量&#xff0c;并且有对这个bean进行读写的操作&#xff0c;因此出现了线程安全的问题。 二、案例代码 2.1、MySpringConfig /*** Author : 一叶浮萍归大海* Date: 2023/10/24 1…

【C++】详解map和set基本接口及使用

文章目录 一、关联式容器与键值对1.1关联式容器&#xff08;之前学的都是序列容器&#xff09;1.2键值对pairmake_pair函数&#xff08;map在插入的时候会很方便&#xff09; 1.3树形结构的关联式容器 二、set2.1set的基本介绍2.1默认构造、迭代器区间构造、拷贝构造&#xff0…