第一讲-BeanFactory和ApplicationContext

第一讲 BeanFactory和ApplicationContext

0. 本系列教程采用的是JDK8版本,SpringBoot 2.5.5版本

1. 容器接口

  1. BeanFactory能做哪些事情?
  2. ApplicationContext有哪些扩展功能
  3. 事件解耦

我们首先看一个简单一点的SpringBoot程序

package com.cherry.chapter1.a01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class A01Application {
    public static void main(String[] args) {
        SpringApplication.run(A01Application.class, args);
    }
}

run方法会有一个返回值,返回的结果就是我们的Spring容器:

ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);

我们看一下ConfigurableApplicationContext的类图(ctrl+Alt+U)

image-20240719193600030

我们发现,ApplicationContext 间接继承了 BeanFactory 接口, 而 ConfigurableApplicationContext 继承了 ApplicationContext 接口, 因此 ConfigurableApplicationContext 也是间接继承了 BeanFactory 的接口。

到底什么才是 BeanFactory接口呢?

  • 它是 ApplicationContext 的父接口
  • BeanFactory 才是 Spring IOC 的核心容器,主要是 ApplicationContext 组合了 BeanFactory 的功能

比如说从IOC容器中获取 Bean 对象,最终还是用BeanFactory中获取!换句话说,我们可以从 ApplicationContext 中获取 BeanFactory 属性,进而从 BeanFactory 中管理 Bean 对象,比如我在下面的代码中打上断点,查看 context 当中的内容:

image-20240719194730012

2. BeanFactory的功能

BeanFactory 能做哪些事情?我们首先看一下 BeanFactory 的定义,看一下该方法的所有方法(ctrl + f12):

image-20240719195108844

比如查看 bean 是否存在,获取 bean 的别名,获取 bean 对象等等。表面上看上去只有 getBean 的一些基本操作,实际上 BeanFactory 的实现类实现了 Bean 的生命周期管理,控制反转,依赖注入等等,不过这些功能都是由 BeanFactory 的实现类实现的,其中一个实现类是 DefaultListableBeanFactory, 我们可以看一下这个类的关系图:

image-20240719195953596

DefaultListableBeanFactory 可以管理所有的 bean 对象,而在管理所有的 bean 对象中,其中大家最熟悉的就是管理单例对象

DefaultListableBeanFactory 使用管理单例 bean 对象的功能,其实使用的就是该类的其中一个父类 DefaultSingletonBeanRegistry

对于查看 Spring 容器中究竟有哪些单例,一种是通过 debug 的方式查看,还有一种是通过反射来获取,我们通过后者尝试获取下其中的单例 bean。

接下来我们看一下Spring容器中的单例 Bean 对象:

package com.cherry.chapter1.a01;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.lang.reflect.Field;
import java.util.Map;

@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中存储单例bean的属性
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        // 由于该属性是私有的,因此要设置允许访问该私有变量
        singletonObjects.setAccessible(true);
        // 获取 beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, Object> map= (Map<String, Object>)singletonObjects.get(beanFactory);
        // 遍历
        map.forEach((k,v)->{
            System.out.println(k+"===>"+v);
        });

    }
}

查看打印结果:(当然了,SpringBoot中单例 bean 非常的多,这里只打印一部分!)

defaultServletHandlerMapping===>null
applicationTaskExecutor===>org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor@160396db
characterEncodingFilter===>org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter@7adbd080
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration===>org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration@7a799159
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration===>org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration@40ab8a8
preserveErrorControllerTargetClassPostProcessor===>org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor@6ff37443
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration===>org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration@65cc8228
org.springframework.context.annotation.internalConfigurationAnnotationProcessor===>org.springframework.context.annotation.ConfigurationClassPostProcessor@53093491
freeMarkerConfiguration===>freemarker.template.Configuration@21719a0
......

现在,我们手动在IOC容器中创建两个Bean:

@Component
public class Component2 {
    
}

@Component
public class Component1 {
    
}

对这两个bean进行过滤

package com.cherry.chapter1.a01;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.lang.reflect.Field;
import java.util.Map;

@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中存储单例bean的属性
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        // 由于该属性是私有的,因此要设置允许访问该私有变量
        singletonObjects.setAccessible(true);
        // 获取 beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, Object> map= (Map<String, Object>)singletonObjects.get(beanFactory);
//        // 遍历
//        map.forEach((k,v)->{
//            System.out.println(k+"===>"+v);
//        });

        map.entrySet().stream().filter(e->e.getKey().startsWith("component"))
                .forEach(e->{
                    System.out.println(e.getKey()+"===>"+e.getValue());
                });
    }
}

测试如下:

component1===>com.cherry.chapter1.a01.Component1@6732726
component2===>com.cherry.chapter1.a01.Component2@474821de

3. ApplicationContext的功能

ApplicationContext相比如BeanFactory多了哪些功能?我们再次查看一下之前的类结构继承图:

image-20240719202743907

我们发现发现ApplicationContext在继承了BeanFactory的基础上,还实现了如下四个功能:

  • MessageSource : 处理国际资源化的能力
  • ApplicationEventPublisher : 发布事件的能力
  • ResourcePatternResolver : 处理路径资源的能力
  • EnvironmentCapable : 处理环境信息的能力

接下来依次介绍上面的四种能力:

3.1 处理国际化资源的能力

在resources文件夹下建立application.properties文件

在resources文件夹下建立messages.properties文件,用于存储通用的翻译结果

在resources文件夹下建立messages_en.properties文件,用于存储英文的翻译结果

hi=hello

在resources文件夹下建立messages_ja.properties文件,用于存储日文的翻译结果

hi=こんにちは

在resources文件夹下建立messages_zh.properties文件,用于存储中文的翻译结果

hi=你好

在主程序中获取各个语言的翻译:

System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));

结果如下:

你好
こんにちは
hello

3.2 处理路径资源的能力

例如寻找classpath下的配置文件,如下的代码:

Resource[] resources = context.getResources("classpath:application.properties");
for (Resource r: resources){
    System.out.println(r);
}

查看运行结果:

class path resource [application.properties]

我们再举个例子,寻找 spring boot 中的 spring.factories 文件看看(需要注意的是 classpath 只是在类路径下寻找,如果想要找的文件在 jar 包里使用 classpath 是找不到的,那如何在 jar 包里找到我们想要的文件呢?那就要在 classpath的后面加上一个星号****,即 classpath):

Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
if (resources != null && resources.length > 0) {
  for (Resource resource : resources) {
    System.out.println(resource);
  }
}

查看运行结果:

URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/boot/spring-boot/2.5.5/spring-boot-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/boot/spring-boot-autoconfigure/2.5.5/spring-boot-autoconfigure-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/spring-beans/5.3.10/spring-beans-5.3.10.jar!/META-INF/spring.factories]
URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/boot/spring-boot-test/2.5.5/spring-boot-test-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/boot/spring-boot-test-autoconfigure/2.5.5/spring-boot-test-autoconfigure-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/opt/homebrew/Cellar/mvn_repository/org/springframework/spring-test/5.3.10/spring-test-5.3.10.jar!/META-INF/spring.factories]

3.3处理环境信息的能力

该功能可以从环境中读取一些配置信息,包括系统缓环境变量,配置文件等中的信息,例如下面的例子:

System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
System.out.println(context.getEnvironment().getProperty("server.port"));

运行结果如下:

/opt/homebrew/Cellar/openjdk@17/17.0.16
8080

3.4 发布事件的能力

首先定义一个事件:

package com.cherry.chapter1.a01;

import org.springframework.context.ApplicationEvent;

// 用户注册事件
public class UserRegisterEvent extends ApplicationEvent {

    // source: 事件源
    public UserRegisterEvent(Object source) {
        super(source);
    }
}

发布事件:

context.publishEvent(new UserRegisterEvent(context));

编写一个监听器,在Spring中任何一个组件都可以作为监听器,监听器用于接收事件:

package com.cherry.chapter1.a01;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class Component2 {
    @EventListener
    public void aaa(UserRegisterEvent event){
        System.out.println(event);
    }
}

运行结果如下:

com.cherry.chapter1.a01.UserRegisterEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@66ac5762, started on Fri Jul 19 21:06:07 CST 2024]

发布事件这个功能的最大功能就是对功能进行解耦合,例如我有这样一个需求,用户注册方式可以有多种,可以是手机号,可以是邮箱等等,为了注册这个功能的扩展性,我们就可以使用发布事件这个功能,例如:

首先编写一个事件发布器:

package com.cherry.chapter1.a01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class Component1 {

    // 事件发布器
    @Autowired
    private ApplicationContext context;

    public void register(){
        System.out.println("用户注册");
        context.publishEvent(new UserRegisterEvent(this));
    }
}

编写一个处理的事件类

 package com.cherry.chapter1.a01;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class Component2 {
    @EventListener
    public void aaa(UserRegisterEvent event){
        System.out.println(event);
        System.out.println("发送短信");
    }
}

从容器中获取事件发布的bean对象并调用refister()方法发布事件

context.getBean(Component1.class).register();

运行结果如下:

用户注册
com.cherry.chapter1.a01.UserRegisterEvent[source=com.cherry.chapter1.a01.Component1@6f231ced]
发送短信
posted @ 2024-07-19 21:27  Cherry_Shen  阅读(24)  评论(0)    收藏  举报