BeanFactory是ApplicationContext的父接口,是真正的Spring核心容器,主要的ApplicationContext实现都【组合】了他的功能。
表面上只有getBean功能,实际上控制反转、基本的依赖注入、直至Bean的生命周期的各种功能,都由他的实现类提供,例如:DefaultListableBeanFactory
。
DefaultListableBeanFactory
不仅仅实现了BeanFactory,还额外拓展了非常多的接口,例如:SingletonBeanRegistry
(单例),其实现类中的私有成员变量DefaultSingletonBeanRegistry#singletonObjects
,包含了所有的单例Bean对象。
接下来,我们可以试着获取到容器中所有的单例对象(因为是私有成员变量,所以需要通过反射来暴力破解)。核心代码如下:
Field field = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
field.setAccessible(true);
// 获取到BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> singletons = (Map<String, Object>)field.get(beanFactory);
从上图可以看出,ApplicationContext对比BeanFactory额外拓展了一些功能,主要是
EnvironmentCapable
、MessageSource
、ApplicationEventPublisher
、ResourcePatternResolver
。
下面,我们逐个分析这几个功能。
MessageSource
:支持项目的国际化ResourcePatternResolver
:通过通配符来获取文件EnvironmentCapable
:获取系统变量或者应用中定义的变量ApplicationEventPublisher
:发布事件共包含3个文件,所有语言通用的messages.properties、中文、英文。
// 测试MessageSource接口的功能
System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
控制台分别输出了你好和hello, Locale一般是浏览器发起请求时,会自动携带的,因此不需要在代码里面写死。
ResourcePatternResolver
可以通过通配符来获取文件,如下:
classpath:
: 从第一个classpath中加载。classpath*:
: 从所有的classpath中加载。file:
: 从文件系统加载资源。jar:
: 用于从 jar 文件加载的 URL 前缀。war:
: 用于从 Tomcat 上的war文件加载的 URL 前缀。// 测试ResourcePatternResolver的功能
Resource[] resources = context.getResources("classpath*:/META-INF/spring.factories");
Lists.newArrayList(resources).forEach(System.out::println);
可以发现,所有的spring.factories文件都被找到了。
可以获取到服务器上的变量或者应用中的变量。变量名称不区分大小写
// 测试EnvironmentCapable的功能
ConfigurableEnvironment environment = context.getEnvironment();
System.out.println("JAVA_HOME:" + environment.getProperty("java_home"));
System.out.println("Server port is: " + environment.getProperty("server.port"));
在我的电脑中配置的JAVA_HOME是大写的,这边写小写,依然可以读取到。
事件发布器是Spring中非常重要的一个机制,属于发布订阅模式。可以用来解耦业务,支持异步执行。
public class UserRegisteredEvent extends ApplicationEvent {
/**
* 用户手机号码
*/
private String phone;
public UserRegisteredEvent(Object source, String phone) {
super(source);
this.phone = phone;
}
}
首先编写一个用户注册事件类,继承于ApplicationEvent
, 有一个自定义的成员变量:phone
/**
* 完成用户注册逻辑
*
* @Date 2023/8/20 11:30
*/
@Component
@Slf4j
public class Component01 {
@Resource
private ApplicationEventPublisher eventPublisher;
public void handleUserRegistered() {
log.info("用户注册~");
// 发布用户已注册事件
eventPublisher.publishEvent(new UserRegisteredEvent(this, "119"));
}
}
Component01处理用户注册逻辑,并且发布了上述事件。
/**
* 事件监听器
*
* @Date 2023/8/20 11:30
*/
@Component
@Slf4j
public class Component02 {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("发送短信了~");
}
}
Component02由于handleUserRegistered
方法上标注了@EventListener
注解,并且方法的参数为UserRegisteredEvent
,说明这个组件监听了该事件。就可以做后续的业务处理了。
// 测试ApplicationEventPublisher的功能
Component01 bean = (Component01) context.getBean("component01");
bean.handleUserRegistered();
最后,在main方法中模拟调用一下,结果如下:
BeanFactory和ApplicationContext并不仅仅是简单的接口继承关系,ApplicationContext组合并扩展了BeanFactory的功能。