BeanFactory与ApplicationContext区别
创建一个springboot项目,对这个函数的main函数来进行执行。
ConfigurableApplicationContext context = SpringApplication.run(BeanFactoryApplicationContextStudyApplication.class, args);
很明显,ConfigurableApplicationContext对象就是一个ApplicationContext派生的对象。观察一下这个类所对应的类图。
从这个类图中,我们可以很明确的看出来,BeanFactory与ApplicationContext存在派生关系。
那么,在以前面试的时候,我们都扯皮所说的容器,依赖注入,控制反转之类的。其实,beanFactory便实现了最基本的容器功能,依赖注入,控制反转都是后期扩展的功能。而ApplicationContext是内部持有了beanFactory来完成这个操作的(策略模式),下面看一下源码。
BeanFactory实际上是一个接口,看一下内部的方法。
可以发现,其实核心方法就是getBean来获取到容器内部存放的对象,也就是证明了BeanFactory这个接口的目的确实是作为容器存放对象,以及取出对象的功能。
至于ConfigurableApplicationContext这个类,我们进入源码看一下,发现它本身也是一个interface接口,也就是说,这里其实是多态展示其名下的某一个子类。
在run方法里不断进行跳入,发现最终底层的一个容器对象为AnnotationConfigServletWebServerApplicationContext。看看所包含的类图,目前为止,我这面似乎注意到AbstractApplicationContext这个类。
点进来看了下,内部持有了一个ApplicationContext的变量parent对象,意思是存在容器也是存在上下层级的吗?那么ApplicationContext采用策略模式所持有的BeanFactory具体是哪个呢?(接着往后学)。
这里卡住了,回归老师所讲的正题。
BeanFactory功能
照老师所讲,这里底层默认持有的BeanFactory类为DefaultListableBeanFactory,去看一下这个类的类图结构。
结合着这趟的类图结构,我好像理解了BeanFactory与ApplicationContext中的关系了。它这面的结构,可以按照装饰者的类图结构来理解。ApplicationContext与BeanFactory的具体实现类中,最上层都是BeanFactory接口,在下方有两类,一个是具体的BeanFactory实现类。另一个则是创建个ApplicationContext接口来对BeanFactory来进行扩展,在ApplicationContext接口下具体实现ApplicationContext实体类,并且持有BeanFactory实体类来对功能进行扩展。个人类图如下:
目前先按照装饰者的思路来理解,应该是对的。
那么DefaultListableBeanFactory上方这么多父层面存在,具体实现了容器功能的是哪个呢?是DefaultSingletonBeanRegistry,看看源码。
如果之前背过面试题循环依赖怎么解决,依赖三级缓存之类的,会对这东西有一定的印象。这俩singleonObjects以及earlySingleonObjects,可不就是一级缓存和二级缓存的存放地吗,这就是容器里要存放的对象的位置。
我们可以试着拿反射来尝试获取一下内部的元素。看着下面这段代码,我上面画的那个类图就完全理解了。
//在 DefaultSingletonBeanRegistry类中获取到singletonObjects对象
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
//获取到ApplicationContext中的beanFactory对象,从这里可以看出策略模式持有
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//在beanFactory中得到容器内部的所有元素
Map<String,Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
//打印最终结果
map.entrySet().stream().filter(stringObjectEntry -> stringObjectEntry.getKey().equals("messageSource"))
.forEach(stringObjectEntry -> System.out.println(stringObjectEntry.getKey()+"++++"+stringObjectEntry.getValue()));
注释里写的很明白了,ApplicationContext(最下级类是GenericApplicationContext)中包含一个getBeanFactory方法,我们可以得到其内部持有的beanFactory对象。beanFacotory中的父层面是存在defaultSingleonBeanRegister的,所以它内部的singleObjects也从父层面继承下来,最后获取到了。就是这么个流程。
ApplicationContext功能
从上面我们已经知道了,applicationContext类实质上是在内部依赖了beanFactory对象来完成操作的。那么,除此之外,它还实现了什么额外功能?
回到类图上,我们可以看到,ApplicationContext除了实现BeanFactory系列的接口,还实现了额外的一些接口。分别为MessageSource,ResourcePatternResolver,ApplicationEventPublisher,EnvironmentCapable这四个接口,下面一一介绍一下。
MessageSource接口
这个接口的功能作用,其实就是我们日常所说的国际化功能。通过在不同配置文件中,配置不同国家的语言,然后在使用过程中来对不同文件中不同语言来进行展示。这块代码整了,但没运行起来,也不算是日常常用的功能,先这么过了。
ResourcePatternResolver接口
ResourcePatternResolver,资源格式解析器,这块其实就是来解析本地资源文件的。如果配置过项目,你是否对一些classpath*:路径,classpath:路径,file:路径这些方式感到眼熟。就像mybatis配置底层的xml访问路径,就是classpath*:路径这种。
classpath:路径1,扫描当前项目下路径1下的所有文件信息。
classpath*:路径2,扫描当前项目下以及底层jar包下的路径2下的所有文件信息
file:路径3,扫描本地磁盘下路径3下的文件信息。
前一段时间看了下网络编程这块吧,书上把这种路径方式称呼为模式:模式特定组成部分。我不确定,网络上的资源路径是否可以通过这种方式来获取呢?后续可以试试,先记一下。
调用方式如下:
//得到项目路径下的application配置文件(getResource得到的感觉不对,结果感觉还是不对,算了,后续再说)
Resource[] resource = context.getResources("classpath:application.properties");
for (Resource resource1 : resource) {
System.out.println(resource1);
}
//获取到底层所有starter对应的spring.factories配置文件内信息
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource1 : resources) {
System.out.println(resource1);
}
EnvironmentCapable接口
EnvironmentCapable,环境能力。这个功能的作用其实就是从配置文件中,或者环境变量中得到相关的配置信息。调用方式如下:
//通过EnvironmentCapable来获取到相关的环境变量,这玩意是个接口,我暂时没有找到引用到这个接口的内容
String java_home = context.getEnvironment().getProperty("java_home");
String port = context.getEnvironment().getProperty("server.port");
System.out.println("java_home:"+java_home);
System.out.println("port:"+port);
ApplicationEventPublisher接口
ApplicationEventPublisher,应用事件发布器,类似发布订阅流程,异步处理的方式。我有一个想法,这个东西,和tomcat内部的listener监听器有没有关系,功能如此像。虽然不了解底层实现,但确实应该是一个东西,有个@EventListener注解。就是个异步处理流程,但假设数量过大的情况下,底层是否用的线程池,怎么实现的,需要好好考虑一下。
写个类试试。
package com.bo.beanfactoryapplicationcontextstudy.one;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Configuration;
import java.time.Clock;
/**
* @Auther: zeroB
* @Date: 2023/1/17 16:39
* @Description: 事件发布器所使用的应用事件对象
*/
public class UserEvent extends ApplicationEvent {
public UserEvent(Object source) {
super(source);
}
public UserEvent(Object source, Clock clock) {
super(source, clock);
}
}
package com.bo.beanfactoryapplicationcontextstudy.one;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
/**
* @Auther: zeroB
* @Date: 2023/1/17 17:00
* @Description: 事件发布方
*/
@Component
public class Compment1 {
@Autowired
public ApplicationEventPublisher applicationEventPublisher;
public String name = "落";
public void register(){
System.out.println("正在注册");
//将当前对象发送出去
applicationEventPublisher.publishEvent(new UserEvent(this));
}
}
package com.bo.beanfactoryapplicationcontextstudy.one;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @Auther: zeroB
* @Date: 2023/1/17 17:00
* @Description: 事件消费方
*/
@Component
public class Compment2 {
@EventListener
public void consume(UserEvent event){
//接收到信息,并获取到结果
System.out.println("发布事件已经被消费");
Compment1 compment1 = (Compment1)event.getSource();
System.out.println("接收过来的名称信息"+ compment1.name);
}
}
在启动类中获取的context对象执行如下语句,确实接收到结果了。
context.getBean(Compment1.class).register();
依赖着容器对象,如果我后续想用这个功能的话,必须获取到容器对象才能做后续的操作。
总结
因为初步学习,所以有很多东西其实并不是很了解,需要自身在后面学习的差不多了,然后再继续巩固。