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

news2024/9/29 15:27:14

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);

    @Override
    public void setBeanName(String name) {
        // 初始化之前回调 BeanNameAware接口
        log.debug("当前bean " + this + " 名字叫:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("当前bean " + this + " 容器是:" + applicationContext);
    }

    @Override
    public 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);

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.debug("注入 ApplicationContext");
    }

    @PostConstruct
    public 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);

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("注入 ApplicationContext");
    }



    @PostConstruct
    public 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);

    // 扩展
    @PostConstruct
    public void init1() {
        log.debug("初始化1");
    }


    // 内置
    @Override
    public 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);

    @PreDestroy
    public void destroy1() {
        log.debug("销毁1");
    }

    @Override
    public 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);

    @PreDestroy
    public void destroy() {
        log.debug("destroy");
    }

}

@Scope("session")
@Component
public class BeanForSession {
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);

    @PreDestroy
    public void destroy() {
        log.debug("destroy");
    }
}

@Scope("application")
@Component
public class BeanForApplication {
    private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);

    @PreDestroy
    public void destroy() {
        log.debug("destroy");
    }
}

@RestController
public class MyController {


    @Lazy
    @Autowired
    private BeanForRequest beanForRequest;

    @Lazy
    @Autowired
    private BeanForSession beanForSession;

    @Lazy
    @Autowired
    private 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()");
    }

    @Autowired
    public 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.coloradmin.cn/o/1140274.html

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

相关文章

ESP32开发日志记录

用过一段时间的ESP8266开发&#xff0c; 是在ubunut下建立的开发环境&#xff0c;现在ESP32更简单&#xff0c;直接在Window下IDE即可完成系统配置、新建工程及编译下载工作&#xff0c;使用起来更加简便 一、生成ESP32的工程运行出错 大概意思的芯片是单核芯片&#xff0c;但…

有什么软件能实现erp、crm、oa、财务系统一体化?

只要你能梳理清楚业务逻辑&#xff0c;没有代码基础也完全可以自己搭建业务系统&#xff0c;比ERP管理系统、工程项目管理系统等等......用简道云就可以轻松实现&#xff1a; 下面来按照题主的需求介绍几个系统&#xff1a; 01 客户关系——CRM客户管理套件 重磅打造客户跟进、…

阿里p8推荐,测试覆盖率工具—Jacoco

测试覆盖率工具 测试过程中根据需求文档和设计文档编写测试用例、执行测试&#xff1b;为了更加全面的覆盖&#xff0c;我们可能还需要理解被测程序的逻辑&#xff0c;需要考虑到每个函数的输入与输出&#xff0c;逻辑分支代码的执行情况&#xff0c;这个时候我们的测试执行情…

鉴源论坛 · 观擎丨基于模型的方法在民机机载软件中的应用

作者 | 蔡喁 上海控安可信软件创新研究院副院长 版块 | 鉴源论坛 观擎 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 01 基于模型的开发和验证MBDV 模型泛指用于支持软件开发过程或软件验证过程的系统的一组软件方面的抽象表示&#xff0c;在机载软件…

【深度学习】【NLP】如何得到一个分词器,如何训练自定义分词器:从基础到实践

文章目录 什么是分词&#xff1f;分词算法使用Python训练分词器步骤1&#xff1a;选择分词算法步骤2&#xff1a;准备训练语料步骤3&#xff1a;配置分词器参数步骤4&#xff1a;训练分词器步骤5&#xff1a;测试和使用分词器 代码示例&#xff1a;使用SentencePiece训练分词器…

技术分享| anyRTC低延时直播优化

直播系统就是把活动现场的音频或视频信号经数字压缩后&#xff0c;传送到直播多媒体服务器(CDN)上&#xff0c;在互联网上供广大网友或授权特定人群收听或收看。而随着技术的日益更新&#xff0c;人民对于直播的互动性&#xff0c;实时性要求更高了&#xff0c;传统的直播少则几…

C语言编写图形化界面-创建按钮-为其指定样式

文章目录 前置章节指定窗口样式给按钮加边框扁平化按钮复选框样式按钮自动复选框 单选按钮三态按钮自动三态按钮 默认按钮样式&#xff08;对话框Enter键&#xff09; 设置按钮位置和大小封装函数 前置章节 开始之前&#xff0c;需要学习以下章节&#xff1a; 创建窗口 窗口过…

10G传输分析仪SDH表TFN D450S参数详情

D450S 10G SDH系列 SDH/SONET传输测试模块 D450S系列SDH/SPNET传输测试模块是TFN模块化测试产品中的一款。D450S系列可配置在FT100测试平台上&#xff0c;实现广泛的PDH/DSn和SDH/SONET的测试功能&#xff0c;为城域网提供综合的测试方案&#xff0c;是高性价比的网络测试产品…

500内部服务器错误很常见,但解决方法也很常见

500内部服务器错误是一个非常通用的HTTP状态代码,这意味着网站的服务器出现了问题,但服务器无法更具体地说明具体问题是什么。 HTTP 500内部服务器错误的原因 大多数情况下,当页面或网站的编程出现问题时,就会出现此错误,但问题肯定在你这边。这些问题可能是由浏览器中的…

Linux系统之file命令的基本使用

Linux系统之file命令的基本使用 一、file命令介绍1.1 Linux简介1.2 file命令简介 二、file命令的使用帮助2.1 file命令的help帮助信息2.2 file命令的语法解释2.3 file命令的man手册 三、文件类型介绍四、file命令的基本使用4.1 查询file版本4.2 显示文件类型4.3 输出时不显示文…

selenium+python web自动化测试框架项目实战实例教程

自动化测试对程序的回归测试更方便。 由于回归测试的动作和用例是完全设计好的,测试期望的结果也是完全可以预料的,将回归测试自动运行... 可以运行更加繁琐的测试 自动化测试的一个明显好处就是可以在很短的时间内运行更多的测试。学习自动化测试最终目的是应用到实际项目中&…

基于springboot实现校友社交平台管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现校友社交平台管理系统演示 摘要 校友社交系统提供给用户一个校友社交信息管理的网站&#xff0c;最新的校友社交信息让用户及时了解校友社交动向,完成校友社交的同时,还能通过论坛中心进行互动更方便。本系统采用了B/S体系的结构&#xff0c;使用了java技…

C++模板类用作参数传递

前言 在模板类<>传递参数的一种实现。记不住&#xff0c;以此记录。 // dome.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #define _CRT_SECURE_NO_WARNINGS #include <iostream> //#include "tools.h" #include <fu…

一种基于屏幕分辨率的RTSP主子码流切换的多路视频监控的播放方案

技术背景&#xff1a; 用户场景下&#xff0c;存在多个监控场所的100路监控摄像头&#xff0c;例如&#xff1a;大华、海康、宇视、杭州宇泛的枪机、球机、半球、NVR、DVR等不同类型的监控设备&#xff0c;通过视频监控平台进行设备的管理&#xff0c;通过RTSP拉流的方案管理监…

python自动化测试平台开发:自动化测试平台简介

一.测试平台简介 为什么需要测试平台 已有的开源测试平台不能满足需要&#xff0c;不要轻易造轮子 需要公司级别的定制 需要整合公司内部的多套平台 例子&#xff1a;DevOps平台、精准化测试平台、质量监控平台等等 常见的测试平台开发模式 大一统模式&#xff08;适合简单的…

鉴源实验室 | AUTOSAR E2E:车载通信的安全保障

作者 | 沈平 上海控安可信软件创新研究院汽车网络安全组 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 随着汽车行业逐步走向电气化、智能化&#xff0c;车载系统的软件和硬件复杂度不断上升。如何确保这些复杂系统中的数据通讯安全和…

Arduino驱动热敏电阻传感器模块

目录 一、简介二、参数性能三、使用方法四、实验现象 一、简介 点击图片购买 热敏电阻传感器模块采用NTC热敏电阻传感器&#xff0c;灵敏度好&#xff0c;PH2.0接口即插即用&#xff0c;防反接&#xff0c;适用于各类开发板。 二、参数性能 电压5V接口3PIN防反接PH2.0杜邦线尺…

BigDecimal应用——计算费用场景中用到Integer,Double,BigDecimal三种类型出现的意外情况 结合BigDecimal源码分析

引出 在一个计算费用的场景中&#xff0c;用到了Integer&#xff0c;Double&#xff0c;BigDecimal三种类型&#xff0c;在转换为bigdecimal的时候遇到的问题&#xff0c;结合源码进行了分析。 1.在new bigdecimal的时候&#xff0c;最好传入的是字符串&#xff1b;2.double类…

Java集合框架:List、Set、Map类型及泛型详解

文章目录 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。&#x1f30e;跑过十五…

干货!BIM高性能3D Web轻量化引擎——HOOPS Communicator!

HOOPS Communicator是一款简单而强大的工业级高性能3D Web可视化开发包&#xff0c;专注于Web端工程图形渲染。采用了先进的流式加载方式&#xff0c;并支持服务端和客户端渲染&#xff0c;是可以在云端进行部署和无缝集成的新技术平台。灵活且易于部署&#xff0c;可在以工程为…