目录
一、BeanFactory和ApplicationContext的关系
3.4.1 ApplicationEventPublisher功能体验
4.1 DefaultListableBeanFactory
5.1 ClassPathXmlApplicationContext
5.2 FileSystemXmlApplicationContext
5.3 AnnotationConfigApplicationContext
5.4 AnnotationConfigServletWebServerApplication
一、BeanFactory和ApplicationContext的关系
先看下springboot的引导类:
@SpringBootApplication
public class A01 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
SpringApplication.run(A01.class, args);
}
}
run方法的返回值是springboot容器,我们来看一下:
ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);
那么ConfigurableApplicationContext是什么呢?咱们来看看类图:
可以看到,ConfigurableApplicationContext是ApplicationContext的子类,而ApplicationContext又间接继承了BeanFactory。
BeanFactory才是Spring的核心容器,主要的ApplicationContext实现都“组合”了BeanFactory的功能。
二、BeanFactory的功能
先看下BeanFactory有哪些接口:
从表面上看,似乎只有getBean方法,但实际上我们还需要看他的实现类:控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能, 都由它的实现类提供。
那么,想要看实现类的功能,我们该从何找起呢?
我们先查看一下springboot中默认的ConfigurableApplicationContext类中的BeanFactory的实际类型,代码如下:
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());
从打印结果可以了解到实际类型为DefaultListableBeanFactory(详见4.1),所以这里以BeanFactory的一个实现类DefaultListableBeanFactory作为出发点,进行分析。他的类图:
我们先看看DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry,看看他的源码:
可以看到有个成员变量singletonObjects,其实这个变量里面保存了springboot所有的单例,我们可以通过反射拿到singletonObjects后将其打印出来,就能看到所有的单例了。
三、ApplicationContext的功能
我们已经了解到,ApplicationContext是BeanFactory的子类,并且我们已经了解到了BeanFactory的功能,那么,我们将着重看看ApplicationContext比BeanFactory多了哪些功能。
可以看到,ApplicationContext除了继承自BeanFactory之外,还继承了以下四个类:
- MessageSource:国际化功能,支持多种语言
- ResourcePatternResolver:通配符匹配资源路径
- EnvironmentCapable:环境信息,系统环境变量,*.properties、*.application.yml等配置文件中的值
- ApplicationEventPublisher:发布事件对象
下面我们来分别研究一下这四个类
3.1 MessageSource
MessageSource拥有国际化功能,支持多种语言。
与MessageSource有关的国际化功能的文件在springboot中默认放在message打头的文件中,我们先建好这些文件:
然后在这些文件里面定义同名的key。比如在message_en.properties
中定义hi=hello
,在messages_ja.propertes
中定义hi=こんにちは
,在messages_zh
中定义hi=你好
,这样在代码中就可以根据这个key hi
和不同的语言类型**获取不同的值了。
编写好代码后,运行起来之后就能看到结果:
Locale.CHINA、Locale.ENGLISH等值在实际项目中会由前端解析到界面所用的语言后传过来。
3.2 ResourcePatternResolver
ResourcePatternResolver可以通过通配符来匹配资源路径。
例1:获取类路径下的messages
开头的配置文件:
Resource[] resources = context.getResources("classpath:messages*.properties");
for (Resource resource : resources) {
System.out.println(resource);
}
例2:获取spring相关jar
包中的spring.factories
配置文件:
resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
3.3 EnvironmentCapable
EnvironmentCapable可以获取系统环境信息或系统环境变量里的值,比如环境变量、*.properties、*.application.yml等配置文件中的值。
//获取系统环境变量中的java_home
System.out.println(context.getEnvironment().getProperty("java_home"));
//获取项目的application.yml中的server.port属性
System.out.println(context.getEnvironment().getProperty("server.port"));
3.4 ApplicationEventPublisher
ApplicationEventPublisher可以用来发布事件。
3.4.1 ApplicationEventPublisher功能体验
想要试试发布事件的功能,我们需要准备三个部分:事件发送类、事件接收(监听)类、事件类。
先看事件类,他继承自ApplicationEvent:
public class UserRegisteredEvent extends ApplicationEvent {
public UserRegisteredEvent(Object source) {
super(source);
}
}
再定义一个事件接受(监听)类,用于监听用户注册事件。类上需要加@Component注解,将该类交给spring
管理。spring中任意个容器都可以作为监听器。然后定义一个处理事件的方法,参数类型为事件类的对象,方法头上需要加上@EventListener
注解。
@Component
@Slf4j
public class UserRegisteredListener {
@EventListener
public void userRegist(UserRegisteredEvent event) {
System.out.println("UserRegisteredEvent...");
log.debug("{}", event);
}
}
再定义一个发送事件的类,就是使用ApplicationEventPublisher的实例对象调用pubulishEvent方法发送,传入的参数是我们刚刚定义好的事件类:
@Component
@Slf4j
public class UserService {
@Autowired
private ApplicationEventPublisher context;
public void register(String username, String password) {
log.debug("新用户注册,账号:" + username + ",密码:" + password);
context.publishEvent(new UserRegisteredEvent(this));
}
}
然后在主启动类中调用一下就可以了:
UserService userService = context.getBean(UserService.class);
userService.register("张三", "123456");
3.4.2 事件有什么用
事件最主要的功能就是解耦。
譬如我们使用事件来做一个用户注册的功能,功能里有用户注册类UserService,用来发送事件,也有用户注册监听类UserRegisteredListener,用于接收事件。由于用户注册后我们有多种后续操作,比如给用户发短信、给用户发邮件或给用户发微信公众号提醒。这样就要求我们的系统有良好的可扩展性,UserService类和UserRegisteredListener类不能耦合在一起,而我们使用事件(如上),就能够实现UserService类和UserRegisteredListener类的解耦:用户注册类UserService发送事件后,我们可以用不同的监听类来接收,不同的监听类做不同的事情;比如我们可以用UserRegisteredListener1来给用户发短信,用UserRegisteredListener2来给用户发邮件。
使用事件进行解耦是一种新的解耦方式,他与AOP方式有什么不同呢?这个值得我们思考。
四、BeanFactory的实现
4.1 DefaultListableBeanFactory
BeanFactory的实现类十分多,我们需要抓住一个重点的实现类去看,这个类就是DefaultListableBeanFactory。
接二(BeanFactory的功能)中所言,Spring底层创建实体类就是依赖于DefaultListableBeanFactory,所以,他是BeanFactory的实现类中最重要的一个。我们有必要使用一下这个类,来模拟Spring使用DefaultListableBeanFactory创建其他实体类对象的过程。
public class TestBeanFactory {
public static void main(String[] args) {
//先创建bean工厂,刚创建的时候是没有任何bean的,我们需要往里面添加bean的定义
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// bean 的定义(即bean的一些描述信息,包含class:bean是哪个类,scope:单例还是多例,初始化、销毁方法等)
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
//把beanDefinition这个bean定义注册进bean工厂,第一个参数是给它起的名字
beanFactory.registerBeanDefinition("config", beanDefinition);
// 打印BeanFactory中Bean
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
// bean1依赖于bean2
@Slf4j
static class Bean1 {
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
public Bean1() {
log.debug("构造 Bean1()");
}
}
@Slf4j
static class Bean2 {
public Bean2() {
log.debug("构造 Bean2()");
}
}
interface Inter {
}
}
这个时候打印出bean工厂中有多少bean,结果只有一个,就是我们刚刚注册进去的config。
那么问题来了,我们在spring实战的知识中得知:当加上@Configuration和@Bean时,容器中会注册这些bean;换句话说,我们此时打印bean工厂的所有bean,理应看到bean1和bean2,而不是只有config。此时只有一个解释能成立:@Configuration和@Bean都没有被解析。那么解析这些注解的功能由谁提供呢?
4.2 BeanFactory的后处理器
BeanFactory本身实现的功能并不多,他的许多功能都是由BeanFactory的后处理器进行扩展的。
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
使用上面的工具类可以为bean工厂添加一些常用的后处理器,此时我们打印bean工厂里的所有bean:
可以看到现在多了一些后处理器从名字也可以大致猜出,他们是处理@Configuration的、@Autowired的……,现在只是把他们加进了bean工厂,还需要让他们工作起来。
public class TestBeanFactory {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// bean 的定义(class, scope, 初始化, 销毁)
AbstractBeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);
// 给 BeanFactory 添加一些常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});
// 打印BeanFactory中Bean
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("构造 Bean1()");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
}
static class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
log.debug("构造 Bean2()");
}
}
}
此时再打印所有的bean,就可以看到bean1和bean2已经出现了。
4.3 Bean的后处理器
在4.2中我们添加了一些后处理器,比如internalConfigurationAnnotationProcessor是处理@Configuration的,它属于BeanFactory的后处理器;而internalAutowiredAnnotationProcessor和internalCommonAnnotationProcessor就属于bean的后处理器,他们是针对 bean 的生命周期的各个阶段提供扩展, 例如 internalAutowiredAnnotationProcessor用于解析@Autowired、internalCommonAnnotationProcessor用于解析@Resource。
如果我们需要让@Autowired 和 @Resource注解也发挥作用,则需要:
// Bean 后处理器, 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource ...
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
.sorted(beanFactory.getDependencyComparator())
.forEach(beanPostProcessor -> {
System.out.println(">>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
这样我们的@Autowired 和 @Resource注解就发挥作用了。请注意,我们这里getBeansOfType()方法传递的参数是BeanPostProcessor.class,是bean的后处理器。
4.4 总结
BeanFactory是一个比较基础的类,他本身并没有特别多的功能,这些事情它不会去做:
- 不会主动调用BeanFactory后处理器
- 不会主动添加Bean后处理器
- 不会主动初始化单例
- 不会解析#{}、${}等
五、ApplicationContext的实现
先看看ApplicationContext的实现类有哪些:
今天我们来介绍四个比较重要的实现类:
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- AnnotationConfigApplicationContext
- AnnotationConfigServletWebServerApplication
5.1 ClassPathXmlApplicationContext
较为经典的容器, 基于 classpath(类路径)下 xml 格式的配置文件来创建ApplicationContext.
5.1.1 使用
创建一个测试类
private static void testClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("a02.xml");
//看一下ApplicationContext中有多少bean
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
//看看bean2中有没有成功注入bean1
System.out.println(context.getBean(Bean2.class).getBean1());
}
static class Bean1 {
}
static class Bean2 {
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
public Bean1 getBean1() {
return bean1;
}
}
创建xml配置文件,并在文件中定义bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 控制反转, 让 bean1 被 Spring 容器管理 -->
<bean id="bean1" class="com.itheima.a02.A02.Bean1"/>
<!-- 控制反转, 让 bean2 被 Spring 容器管理 -->
<bean id="bean2" class="com.itheima.a02.A02.Bean2">
<!-- 依赖注入, 建立与 bean1 的依赖关系 -->
<property name="bean1" ref="bean1"/>
</bean>
</beans>
运行结果:
5.1.2 原理
我们模拟一下加载xml文件的过程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通过 XmlBeanDefinitionReader 到xml文件中读取bean的配置信息,将这些bean加载到bean工厂中。
public static void main(String[] args) {
//先实现DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("读取之前...");
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println("读取之后...");
//然后到xml文件中读取bean的定义信息
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
5.2 FileSystemXmlApplicationContext
基于磁盘路径下 xml 格式的配置文件来创建ApplicationContext。
5.2.1 使用
编写一个测试类:
private static void testFileSystemXmlApplicationContext() {
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(
"src\\main\\resources\\a02.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
//看看bean2中有没有成功注入bean1
System.out.println(context.getBean(Bean2.class).getBean1());
}
static class Bean1 {
}
static class Bean2 {
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
public Bean1 getBean1() {
return bean1;
}
}
xml文件与5.1.1中为同一个。运行结果也同5.1.1
5.2.2 原理
我们模拟一下加载xml文件的过程即可明白原理了:先初始化出DefaultListableBeanFactory,然后通过 XmlBeanDefinitionReader 到xml文件中读取bean的配置信息,将这些bean加载到bean工厂中。
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("读取之前...");
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println("读取之后...");
//然后到xml文件中读取bean的定义信息
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
5.3 AnnotationConfigApplicationContext
5.3.1 使用
较为经典的容器, 基于 java 配置类来创建ApplicationContext。
private static void testAnnotationConfigApplicationContext() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2(Bean1 bean1) {
Bean2 bean2 = new Bean2();
bean2.setBean1(bean1);
return bean2;
}
}
static class Bean1 {
}
static class Bean2 {
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
public Bean1 getBean1() {
return bean1;
}
}
运行结果:
可以看到,结果与5.1.1和5.2.1有不同,因为我们的配置类Config也默认为一个bean注入进了bean工厂;除此之外,AnnotationConfigApplicationContext 还自动帮我们加了五个后处理器。
5.4 AnnotationConfigServletWebServerApplication
较为经典的容器, 基于 java 配置类来创建ApplicationContext, 用于 web 环境。
我们创建一个测试类来使用它:
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class WebConfig {
//tomcat容器
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
//前控制器
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
//让前控制器运行在Tomcat容器中
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
//控制器
@Bean("/hello")
public Controller controller1() {
return (request, response) -> {
response.getWriter().print("hello");
return null;
};
}
}
可以看到后台打印出很多的bean。
从这里可以了解到:
- springboot内嵌了Tomcat,我们无需手动添加Tomcat容器,也可以运行bean
- springboot的所有请求都要经过dispatchServlet(前控制器),然后再走到我们自己的控制器中
- 通过DispatcherServletRegistrationBean 可以将DispatcherServlet 注册到Tomcat中