BeanFactory 与 ApplicationContext 区别

140 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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:component1component2
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,其中就有我们自定义的 component1component2
  • 我们也可以对 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());
            });
}
  • 这样输出的就只有 component1component2

ApplicationContext

  • 我们可以看到,ApplicationContext接口相较于 BeanFactory 的扩展功能主要是体现在它继承的以下四个接口上
  • MessageSource:处理国际化资源
  • EnvironmentCapable:处理环境信息
  • ApplicationEventPublisher:发布事件对象
  • ResourcePatternResolver:通配符匹配资源(磁盘/类路径文件)

MessageSource

  • 我们先来写一下 message 的配置文件
    • messages.properties:空
    • messages_en.properties:hi=Hello
    • messages_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                   : 发送短信