携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情
BeanFactory 与 ApplicationContext 的联系
- 我们首先来看如下类关系图,可以看到 BeanFactory 是 ApplicationContext 的父类
- BeanFactory 是 Spring 的核心容器,主要的 ApplicationContext 实现都【组合】了它的功能,比如我们调用
getBean方法,实际上调用的是 BeanFactory 的 getBean 方法 - 上面我们提到了【组合】,为什么是组合呢?这是因为 BeanFactory 是 ApplicationContext 的一个成员变量
BeanFactory 功能
- 查看 BeanFactory 接口中的方法,我们可以发现表面上它的功能并不多,常用的也就是
getBean - 实际上控制反转、基本的依赖注入,直至 Bean 的生命周期的各种功能,都由它的实现类
DefaultListableBeanFactory提供 - 我们看一下
DefaultListableBeanFactory管理的单例对象,单例对象是在它的父类DefaultSingletonBeanRegistry singletonObjects集合中放的就是所有的单例,key 是 bean 的名称,value 是对象的实例,由于这个属性是私有的,我们无法直接看,可以通过反射拿到它进行查看- 首先自定义两个 bean:
component1和component2
package com.ruochen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class Component1 {
private static final Logger log = LoggerFactory.getLogger(Component1.class);
}
package com.ruochen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class Component2 {
private static final Logger log = LoggerFactory.getLogger(Component2.class);
}
- main 函数中代码如下
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
map.forEach((k, v) -> {
System.out.println(k + "=" + v);
});
}
- 我们可以看到输入很多的 bean,其中就有我们自定义的
component1和component2 - 我们也可以对 map 集合过滤一下,只输出我们自己定义的 bean
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
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和component2
ApplicationContext
- 我们可以看到,
ApplicationContext接口相较于BeanFactory的扩展功能主要是体现在它继承的以下四个接口上 MessageSource:处理国际化资源EnvironmentCapable:处理环境信息ApplicationEventPublisher:发布事件对象ResourcePatternResolver:通配符匹配资源(磁盘/类路径文件)
MessageSource
- 我们先来写一下 message 的配置文件
messages.properties:空messages_en.properties:hi=Hellomessages_ja.properties:hi=你好messages_zh.properties:hi=扣你几哇
- 测试
// 根据 key 找到不同翻译结果
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.JAPANESE));
你好
Hello
扣你几哇
ResourcePatternResolver
Resource[] resources = context.getResources("classpath:application.properties");
for (Resource resource : resources) {
System.out.println(resource);
}
resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
class path resource [application.properties]
URL [jar:file:/D:/Java/maven/repository/org/springframework/boot/spring-boot/2.5.13/spring-boot-2.5.13.jar!/META-INF/spring.factories]
URL [jar:file:/D:/Java/maven/repository/org/springframework/boot/spring-boot-autoconfigure/2.5.13/spring-boot-autoconfigure-2.5.13.jar!/META-INF/spring.factories]
URL [jar:file:/D:/Java/maven/repository/org/springframework/spring-beans/5.3.19/spring-beans-5.3.19.jar!/META-INF/spring.factories]
EnvironmentCapable
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));
D:\Java\jdk\jdk18
8080
ApplicationEventPublisher
- 事件类
UserRegisteredEvent.java(source 代表事件源)
package com.ruochen;
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
public UserRegisteredEvent(Object source) {
super(source);
}
}
- 再写一个监听器用来收事件
package com.ruochen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class Component2 {
private static final Logger log = LoggerFactory.getLogger(Component2.class);
@EventListener
public void userRegister(UserRegisteredEvent event) {
log.debug("{}", event);
}
}
2022-05-04 21:59:21.581 DEBUG 29092 --- [ main] com.ruochen.Component2 : com.ruochen.UserRegisteredEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4c402120, started on Wed May 04 21:59:20 CST 2022]
- 测试
context.publishEvent(new UserRegisteredEvent(context));
- 从上面一个小测试中我们了解到了事件的基本使用,那么事件有什么用呢?其实就是一个解耦方式,我们设想这样一种场景:我们现在有两个 component,比如 component 是做用户注册功能的,然后注册完要做一些后续的操作(比如给用户发送一个注册成功的短信、邮件之类的),这个功能可能变化的,那么我们就不能将它写死,这时候就可以用到事件
- 我们在 component1 中注册后发送一个事件
package com.ruochen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class Component1 {
private static final Logger log = LoggerFactory.getLogger(Component1.class);
@Autowired
private ApplicationEventPublisher context;
public void register() {
log.debug("用户注册");
context.publishEvent(new UserRegisteredEvent(this));
}
}
- 在 component2 中接收这个事件,进行后续操作
package com.ruochen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class Component2 {
private static final Logger log = LoggerFactory.getLogger(Component2.class);
@EventListener
public void sendSms(UserRegisteredEvent event) {
log.debug("{}", event);
log.debug("发送短信");
}
}
- 这样就实现了用户注册与发送短信的解耦,我们可以来测试一下
context.getBean(Component1.class).register();
2022-05-04 22:07:22.264 DEBUG 32236 --- [ main] com.ruochen.Component1 : 用户注册
2022-05-04 22:07:22.264 DEBUG 32236 --- [ main] com.ruochen.Component2 : com.ruochen.UserRegisteredEvent[source=com.ruochen.Component1@5491f68b]
2022-05-04 22:07:22.264 DEBUG 32236 --- [ main] com.ruochen.Component2 : 发送短信