1_关于BeanFactory与ApplicationContext的联系和区别

news/2025/1/20 21:52:09/文章来源:https://www.cnblogs.com/lilyflower/p/18238758

BeanFactory与ApplicationContext

1. 容器和接口

1.1 BeanFactory与ApplicationContext的联系与区别:

ConfigurableApplicationContext 是 ApplicationContext的子接口,而ApplicationContext接口又是 BeanFactory 的子接口。因此ConfigurableApplicationContext 接口简介继承了 BeanFactory 的接口。注意,BeanFactory 才是Spring 容器的核心实现!而ApplicationContext只是组合了BeanFactory的功能。例如 ApplicationContext 中的 getBean() 方法的就是首先获取到 BeanFactory,然后再调用中的 BeanFactory.getBean()方法

@Override
public Object getBean(String name) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(name);
}

编写一段代码:

@SpringBootApplication
public class A01Application {private static final Logger log = LoggerFactory.getLogger(A01Application.class);public static void main(String[] args){ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context);	// 在此处打断点调试}
}

image-20240608120353186

就会发现,ApplicationContext对象其实包含了一个BeanFactory对象,自然也就包含了BeanFactory中定义的核心方法。

1.2 BeanFactory能做什么事情?

查看BeanFactory对外提供了哪些方法?(ctrl+f12)

image-20240608120749434

在 BeanFactory 接口中定义了许多获取bean的方法,表面上只有 getBean(), 实际上控制反转,依赖注入,bean的生命周期管理以及功能都是由 BeanFactory 的实现类提供。BeanFactory 的真正实现类就是 DefaultListableBeanFactory。其中 DefaultListableBeanFactory 可以管理管理单例对象,而对单例对象的管理是在它的父类 DefaultSingletonBeanRegistry 中实现的。

该类不仅仅实现了对bean的单例管理,还有循环依赖的解决,以及三级缓存的处理。在后面依次分析。

手动编写两个组件 Component1和Component2:

@Component
public class Component1 { }package com.cherry.a01;//..............@Component
public class Component2 { }

然后通过反射的机制拿到DefaultSingletonBeanRegistry的存放单例的属性,判断这两个组件是否存在:

@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context);// 首先获取DefaultSingletonBeanRegistry类中的私有属性singletonObjects,该属性记录了Spring容器中运行时的单例Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");// 由于是似有属性,要设置该属性可访问singletonObjects.setAccessible(true);// 获取bean对象ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);map.entrySet().stream().filter(e -> e.getKey().startsWith("component")).forEach(e -> {System.out.println(e.getKey() + "=" + e.getValue());});}
}
component1=com.cherry.a01.Component1@784abd3e
component2=com.cherry.a01.Component2@36c2b646

1.3 ApplicationContext有哪些扩展功能

首先查看 ApplicationContext 接口的定义:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { }

ApplicationContext 继承了如下四个接口:

  1. MessageSource:该类具备处理国际化资源的能力
  2. ResourcePatternResolver:该类具备资源匹配的能力
  3. ApplicationEventPublisher:该类具备发布事件的能力
  4. EnvironmentCapable:该类具备获取系统环境的能力

1.3.1 处理国际资源

首先编写通用语言翻译结果的配置文件 messages.properties

其次依次编写不同的语言翻译配置文件,以及写入如下信息:

  • messahes_en.proeprties ---> hi=hello
  • messages_zh.properties ---> hi=你好
  • messages_ja.properties ---> hi=こんにちは

编写代码使用 ApplicationContext 对象进行翻译:

@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context.getMessage("hi", null, Locale.CHINA));System.out.println(context.getMessage("hi", null, Locale.ENGLISH));System.out.println(context.getMessage("hi", null, Locale.JAPAN));}
}

1.3.2 处理资源

该类主要用于获取各种资源,例如下面的获取resources下的application的配置文件:

@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);Resource[] resources = context.getResources("classpath*:/application.properties");for(Resource res:resources){System.out.println(res);}}
}
file [/home/yihaoshen/Spring-Project/show/target/classes/application.properties]

1.3.3 获取当前系统资源(配置信息)

编写如下代码获取当前JDK版本以及当前服务器端口号:

@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));System.out.println(context.getEnvironment().getProperty("server.port"));}
}
/opt/development_tools/jdk21/jdk-21.0.3
9000

1.3.4 发布事件(最大作用就是实现组件或功能的解偶!)

首先编写一个事件:

public class UserRegisterEvent extends ApplicationEvent {// 参数:事件源(谁发布的事件)public UserRegisterEvent(Object source) {super(source);}
}

其次再编写一个事件监听器(用于处理事件)

@Component
public class Component2 {// 编写一个监听器,用于接收事件,其参数为发布事件的类型@EventListener	//该注解表明该方法专门用于监听事件public void receive(UserRegisterEvent event){System.out.println(event.toString());}
}

在ApplicationContext中使用发布事件的功能:

@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);context.publishEvent(new UserRegisterEvent(context));}
}
com.cherry.a01.UserRegisterEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@7ef82753, started on Sat Jun 08 13:07:15 CST 2024]

2. 容器实现

2.1 BeanFactory 实现的特点

由前面可知,BeanFactory 的默认实现是DefaultListableBeanFactory,我们可以在BeanFactory中添加一些bean的定义,然后BeanFactory根据Bean的定义创建Bean对象。

关于Bean的定义,包含如下部分:

  1. Bean的类型(class)
  2. 该Bean的作用域(scope),单例还是多例
  3. 该Bean有无初始化方法,有无销毁方法等

首先编写Bean1, Bean2:

public class Bean1 {public Bean1(){System.out.println("构造 Bean1()");}@Autowiredprivate Bean2 bean2;public Bean2 getBean2(){System.out.println("Get Bean2对象");return bean2;}
}// ===============public class Bean2 {public Bean2(){System.out.println("构造 Bean2()");}
}

编写配置类:

@Configuration
public class Config {@Beanpublic Bean1 bean1(){return new Bean1;}@Beanpublic Bean2 bean2(){return new Bean2;}
}

首先创建一个关于Config类的BeanDefinition对象, 并设置为单例模式:

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();

其次将创建好的而BeanDefinition对象加入到beanFactory中,并为该beanDefineition命名:

beanFactory.registerBeanDefinition("config", beanDefinition);

完整代码如下:

@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinitionString[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}}
}

输出结果为:

config

此时我们发现,命名我们已经在Config类里面已经为bean1和bean2两个属性加上了@Bean注解,但是并没有将该两者的对象加入到容器中,换句话说,容器中并没有包含对@Bean注解标记的 BeanDefinition,由此看出 BeanFactory缺少了注解解析的能力。

此时就需要我们为beanFactory加入一些后处理器来实现支持对其它注解的解析的能力:

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

此时再来查看当前容器中包含哪些beanDefine:

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor //解析2Autowired注解
org.springframework.context.annotation.internalCommonAnnotationProcessor	//解析@Resource注解
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

此时我们还没找到Bean1和Bean2这两个bean, 原因是我们虽然加入了上面这些处理更多功能的后处理器,但没有执行后处理器,只有执行后处理器才能呢过真正的获取到bean对象,完整代码如下:

@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinition// 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,// 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);// 执行beanFactory后处理器beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);});String[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}}
}

运行结果如下:

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2

此时判断bean1对象能否正常使用:

Bean1 bean1 = beanFactory.getBean(Bean1.class);
System.out.println(bean1.getBean2());
bean1
bean2
构造 Bean1()
Get Bean2对象
null

此时发现bean1.getBean2()获取bean2对象竟然为空,这是为什么?

@Autowired
private Bean2 bean2;

原因在于@Autowired注解并未没解析处理,因为2Autowired注解解析是由bean的后处理器完成的,并不是beanFactory的后处理器完成的。因此我们需要添加对@Autowired注解解析的bean后处理器,完整代码如下:

@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinition// 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,// 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);// beanFactory 后处理器,补充一些对bean的定义beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);});// bean 后处理器:针对bean的生命周期各个阶段来扩展bean的功能,例如@Autowired解析,@Resource的解析等beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);String[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}Bean1 bean1 = beanFactory.getBean(Bean1.class);System.out.println(bean1.getBean2());}
}

此时发现bean1对象可以正常使用了!

config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
构造 Bean1()
构造 Bean2()
Get Bean2对象
com.cherry.a02.Bean2@45752059

此外我们还发现了,容器中只包含了对bean的定义,只有需要使用某一个bean的时候没,才会根据bean定义创建bean对象供给调用者使用!当然如果我们希望在使用bean对象之前就创建好bean对象,我么可以调用如下这个方法:

beanFactory.preInstantiateSingletons();	// 提前准备好单例

关于BeanFactory的总结:

  1. 不会主动调用BeanFactory的后处理器
  2. 不会主动添加Bean的后处理器
  3. 不会主动初始化单例对象,只有需要用到的时候才会实例化所需要的Bean对象

关于注解排序问题:

如果我们在一个属性或方法上添加了多个注解,那么注解解析是怎么样的?

此时的解析顺序就和后处理器的添加顺序有关,谁先加入了后处理器,哪个后处理器的优先级就高,谁就先做注解的解析。

2.2 ApplicationContext 的常见实现和用法

关于ApplicationContext接口,有如下4个经典的实现:

  1. ClassPathXmlApplicationContext:基于classpath下的xml格式的Spring配置文件
  2. FileSystemXmlApplicationContext:基于磁盘路径下xml格式的Spring配置文件
  3. AnnotationConfigApplicationContext:基于java的配置类来创建
  4. AnnotationConfigServletWebServerApplicationContext:基于java的配置类来创建,用于WEB环境

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

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

相关文章

回溯法求解TSP问题

1.readme 1>python 2>代码基于具体的实例,如有需要可自行修改问题规模为n,不再赘述 2.code 代价矩阵 999表示无穷 arc = [[999, 3, 6, 7], [5, 999, 2, 3], [6, 4, 999, 2], [3, 7, 5, 999]] city 存放除出发点0外的城市 city = [3, 2, 1] 访问标志数组 0-未访问 1-已访…

OpenGL:混合

OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的…

下水道拾的沟槽的文章

wow天色将晚,在我看着你的眼里色彩斑斓 五月的气候合往年一样闷热,二中的晚自习太静,于是闲下来了,止不住地去向,好像又来一轮毕业的时候。小学时,谈起毕业总是很害怕的,那时觉得毕业时候一定是要大哭一场,不然就算不上毕业结果那天也如同流水一般平淡地过去了,正如过…

P3756 [CQOI2017] 老C的方块 解题报告

P3756 [CQOI2017] 老C的方块 解题报告oj:https://gxyzoj.com/d/gxyznoi/p/P266 又是网格题,考虑染色:显然可以发现,每一个不合法的图形都可以被染成黄->蓝->特殊边->绿->红,且旋转后同样满足条件 推广到整个棋盘就是:所以是否可以将颜色编号,然后按照上述方法…

[stars-one] 星念轻小说下载器

原文地址: [stars-one] 星念轻小说下载器-Stars-One的杂货小窝 一款将在线轻小说保存到本地的下载工具 软件介绍小说单卷下载 小说全卷下载(需VIP) 多线程解析和下载 下载导出为epub文件 自动更新软件使用前需要进行用户登录(邮箱注册) 采用会员订阅制,PC版和Android版共用账号…

裁剪序列Cut the Sequence

首先,我们可以先想一想朴素算法,推出DP,i表示分了几段,则可以推出$$F[i]=min_{1<=j<=i}(f[j]+max_{j+1<=k<=i}(a[k]))$$点击查看代码memset(f,0x3f,sizeof f);f[0]=0;for(int i=1;i<=n;i++){for(int j=0;j<i;j++){int tmp=0;ll sum=0;for(int k=j+1;k<…

vits-simple-api搭建

根据vits-simple-api中文文档指南自行搭建后端 以下步骤均在windows平台cpu推理搭建为例选择你的vits模型(注意是vits!不是So-Vits Bert Vits2 Gpt Vits)建议去抱脸网搜索或者b站搜素以及自己训练.在vits-simple-api的路径的model目录下新建你下载模型的名字的文件夹将模型的js…

bili-emoji自定义表情包设置

使用图床上传图片.推荐使用聚合图床,简单免费 把图片的图床链接复制到一个txt文件中,如abc.txt 将abc.txt放到koishi目录的非node_modules\koishi-plugin-emojihub-bili文件夹中,如koishi\lumia\abc.txt 在插件界面填写要触发的命令以及路径如下图所示5.使用如下图所示

关于LTspice如何导入第三方的.lib文件进行仿真

转载自:https://bbs.eeworld.com.cn/thread-1265324-1-1.html 1.在芯片官网找到对应的PSPICE模型下载后,将.lib文件移入到路径下的sub文件夹中。(例如C:\Users\\username\Documents\LTspiceXVII\lib\sub) 2.将.lib文件拖入LTspice后右键单击.subckt后的芯片名称,选择Creat S…

OOP题目集4~6的总结

目录(一)前言 (二)作业介绍 (三)算法与代码 (四)PowerDesigner类图模型 (五)SourceMonitor代码分析 (六)自学内容 (七)总结一、前言介绍本篇博客的大致内容、写作目的、意义等本篇博客介绍如何使用Java语言基础和算法来解决题目问题,在此基础上进行对最近Java编…

前端使用 Konva 实现可视化设计器(13)- 折线 - 最优路径应用【思路篇】

这一章把直线连接改为折线连接,沿用原来连接点的关系信息。关于折线的计算,使用的是开源的 AStar 算法进行路径规划,启发方式为 曼哈顿距离,且不允许对角线移动。请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示…

SpringBoot热部署设置

在使用SpringBoot进行开发过程中,我们往往会对代码进行反复修改并对项目进行部署查看效果,这时反复重启SpringBoot会很麻烦,因此使用热部署是提高开发效率的必备插件——“spring-boot-starter-test” <!--SpringBoot热部署依赖--> <dependency> <gr…