红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 未分类
  3. 正文

Spring 源码学习 1:ApplicationContext

2025年6月19日 22点热度 0人点赞 0条评论

Bean 定义和 Bean 实例

AnnotationConfigApplicationContext

首先,创建一个最简单的 Spring Boot 应用。

在入口类中接收SpringApplication.run的返回值:

@SpringBootApplication
public class DemoApplication {
​
    public static void main(String[] args) {
​
        ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        System.out.println(context);
​
    }
​
}

ConfigurableApplicationContext是一个接口,查看其继承关系:

image-20250618101827464

比较重要的父接口有ApplicationContext和BeanFactory。

打上断点,启动调试模式,可以看到实际运行时使用的真实类型:

image-20250618102023491

查看AnnotationConfigApplicationContext的继承关系比较复杂,最值得注意的:

image-20250618102843929

GenericApplicationContext实现了抽象类AbstractApplicationContext,可以看做是ApplicationContext接口的一个通用实现。其包含一个beanFactory属性:

private final DefaultListableBeanFactory beanFactory;

DefaultListableBeanFactory

老版本的 Spring 会直接使用DefaultListableBeanFactory作为容器实现,新版本的 Spring 在外边又包装了一层。SpringBean 的注册、获取等操作都由DefaultListableBeanFactory实现,它包含一个属性beanDefinitionMap:

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

这个 Map 保存了 Spring Bean 的定义,key 则是 Spring Bean 的名称。

可以用代码的方式打印当前项目中已经注册的 Bean 定义:

ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
System.out.println(context);
AnnotationConfigApplicationContext acaContext = (AnnotationConfigApplicationContext) context;
ConfigurableListableBeanFactory beanFactory = acaContext.getBeanFactory();
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanFactory.getBean(beanDefinitionName));
}

image-20250618111633404

可以看到,即使没有任何自定义的 Bean,默认情况下 Spring 框架也会注册很多必要的 Bean,甚至包含了AnnotationConfigApplicationContext容器自己。

如果添加了自定义 Bean,在这里也会看到。

DefaultSingletonBeanRegistry

查看DefaultListableBeanFactory的继承关系:

image-20250618112617884

它有一个基类DefaultSingletonBeanRegistry,这个类使用一个 Map 结构保存所有的单例 Bean:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

同样,可以用代码的方式打印 Bean 对象:

DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)acaContext.getBeanFactory();
DefaultSingletonBeanRegistry registry = beanFactory;
String[] singletonNames = registry.getSingletonNames();
for (String name : singletonNames) {
    System.out.println(registry.getSingleton(name));
}

Bean 对象的打印结果要多于 Bean 定义,这是显而易见的,因为同一个 Bean 定义可以生成多个 Bean 对象:

image-20250618113755248

小结

从上面的分析不难看出,AnnotationConfigApplicationContext的设计采用了代理(委托)模式,它包含一个 DefaultListableBeanFactory 类型的 BeanFactory,具体的 Bean 定义和 Bean 实例都保存在其中,而AnnotationConfigApplicationContext 本身所有对 Bean 的注册、获取等操作都代理给DefaultListableBeanFactory 的对应方法实现。而DefaultListableBeanFactory 和AnnotationConfigApplicationContext 都实现了基本的BeanFactory接口,这也正是代理模式的基础。

其它功能

上面介绍了 Bean 工厂的核心功能——维护 Bean 定义和 Bean 实例。事实上 ApplicationContext 除了继承 BeanFactory 的相关接口,还继承了其它的几个接口:

image-20250618114816873

EnvironmentCapable

可以利用这个接口获取环境信息,比如系统环境变量和项目的配置信息:

private static void printEnvironment(EnvironmentCapable environmentCapable) {
    Environment environment = environmentCapable.getEnvironment();
    String property = environment.getProperty("spring.application.name");
    System.out.println(property);
    String javaHome = environment.getProperty("java_home");
    System.out.println(javaHome);
}

注意,在获取系统环境变量时,是大小写不敏感的,比如这里的 getProperty("java_home"),实际上系统中配置的环境变量是大写的JAVA_HOME,但这里依然可以获取到。

ApplicationEventPublisher

是 Spring 事件框架的一部分,可以用它来发布事件:

private static void testApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    applicationEventPublisher.publishEvent(new UserUpdatedEvent(applicationEventPublisher, 1L));
}

UserUpdatedEvent是用户自定义事件,代表用户数据被更新,提示事件处理程序进行相应处理(比如更新用户缓存):

public class UserUpdatedEvent extends ApplicationEvent {
    @Getter
    private final Long userId;
    public UserUpdatedEvent(Object source, Long userId) {
        super(source);
        this.userId = userId;
    }
}

要处理这个事件,需要添加事件监听:

@Component
public class UserListener {
    @EventListener
    public void userUpdatedEventHandler(UserUpdatedEvent userUpdatedEvent){
        Long userId = userUpdatedEvent.getUserId();
        System.out.println("User updated: " + userId);
    }
}

resourcePatternResolver

可以用这个接口获取项目的资源文件:

private static void printResource(ResourcePatternResolver resourcePatternResolver) throws IOException {
    Resource resource = resourcePatternResolver.getResource("classpath:application.properties");
    BufferedReader reader = FileUtil.getReader(resource.getFile(), StandardCharsets.UTF_8);
    do{
        String line = reader.readLine();
        System.out.println(line);
    }
    while(reader.ready());
}

这里使用了 hutool 依赖。

classpath:xxx只会返回在 classpath 下检索到的第一个匹配的资源文件。如果要检索所有的 classpath 路径中匹配到的资源文件(比如包含其它 jar 包中的资源文件),需要使用classpath*:xxx:

private static void printResources(ResourcePatternResolver resourcePatternResolver) throws IOException {
    Resource[] resources = resourcePatternResolver.getResources("classpath*:META-INF/spring.factories");
    for(Resource resource : resources){
        System.out.println(resource.getFilename());
    }
}

打印当前项目使用的 classpath:

private static void printClassPaths(){
    String classpath = System.getProperty("java.class.path");
    String[] classpathArr = classpath.split(";");
    for (String classpathStr : classpathArr) {
        System.out.println(classpathStr);
    }
}

image-20250618123542653

可以看到结果中包含了通过 Maven 导入的 jar 包。

MessageSource

MessageSource 可以实现国际化。

添加国际化相关资源文件:

image-20250619115108598

messages_en_US.properties:

login.title=User Login
login.username=Username

messages_zh_CN.properties:

login.title=用户登录
login.username=用户名

在配置文件application.properties中添加配置信息:

spring.messages.basename=messages/messages
spring.messages.encoding=UTF-8

使用 MessageSource 按照语言地域输出信息:

private static void testMessageResource(MessageSource messageSource) {
    String enTitle = messageSource.getMessage("login.title", null, Locale.US);
    String enUserName = messageSource.getMessage("login.username", null, Locale.US);
    String cnTitle = messageSource.getMessage("login.title", null, Locale.CHINA);
    String cnUserName = messageSource.getMessage("login.username", null, Locale.CHINA);
    System.out.println(String.format("%s %s", enTitle, enUserName));
    System.out.println(String.format("%s %s", cnTitle, cnUserName));
}

BeanFactory

DefaultListableBeanFactory是 BeanFactory 的一个主要实现,下面展示如何使用它创建 Bean 实例:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
this.print(beanFactory);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)
    .setScope(BeanDefinition.SCOPE_SINGLETON)
    .getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);
this.print(beanFactory);

这里的Config是一个配置类:

@Configuration
static class Config {
    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

方便起见这些演示用的类都是静态内部类。

Bean1 和 Bean2 定义:

static class Bean1 {
}

static class Bean2 {
    @Autowired
    private Bean1 bean1;
}

打印方法:

private void print(DefaultListableBeanFactory beanFactory) {
    String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

实际执行会发现结果中仅包含一个 Bean 定义:

config

也就是说,配置类中的 Bean 方法并没有生效。

工厂后处理器

实际上,处理@config 注解标注的配置类中的 Bean 方法,是 BeanFactory 后处理器(Post Prosessor)的工作。因此这里需要为 BeanFactory 添加后处理器:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 添加后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

现在输出的结果中可以看到 BeanFactory 中添加了很多工厂后处理器的 Bean 实例:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

仅将工厂后处理器实例添加到 BeanFactory 是不够的,还需要在工厂加载完所有 Bean 定义后调用处理器:

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
this.print(beanFactory);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)
    .setScope(BeanDefinition.SCOPE_SINGLETON)
    .getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);
beanFactoryPostProcessorMap.forEach((k,v)->{
    v.postProcessBeanFactory(beanFactory);
});
this.print(beanFactory);

这些工厂后处理器都实现了BeanFactoryPostProcessor这个接口,因此可以通过getBeansOfType(BeanFactoryPostProcessor.class)方式从容器中获取。

现在再执行,就能看到:

config
bean1
bean2

BeanFactory 已经正确处理了配置类的 Bean 方法,将相应的 Bean 定义添加了进来。

现在再测试从 BeanFactory 中获取 Bean 实例:

System.out.println(beanFactory.getBean(Bean1.class));
Bean2 bean2 = beanFactory.getBean(Bean2.class);
System.out.println(bean2);
System.out.println(bean2.getBean1());

会输出:

com.example.demo.DemoApplicationTests$Bean1@4bc33720
com.example.demo.DemoApplicationTests$Bean2@2dd0f797
null

也就是说在Bean2中通过@Autowired注解注入的Bean1的依赖并没有生效,因此这里输出的是null。

在 Spring 中处理依赖注入(Dependency Inject)的是 Bean 的后处理器。

Bean 后处理器

其实之前通过AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory),已经为工厂添加了相应的 Bean 后处理器定义。比如打印结果中:

...internalAutowiredAnnotationProcessor # 处理 @Autowired 注解的后处理器
...internalCommonAnnotationProcessor # 处理 @Resource 注解的后处理器

因此这里只需要获取处理器,然后添加到工厂中就行了:

// 为工厂添加 Bean 的后处理器
Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class);
beanPostProcessorMap.forEach((k, v) -> {
    beanFactory.addBeanPostProcessor(v);
});

现在获取 Bean 实例时工厂就能够正确处理依赖注入了。

如果在依赖注入时,同时使用了两种注解(@Autowired 和 @Resource),哪个会生效?

定义示例类型:

interface Inter {}

static class Bean3 implements Inter {}
static class Bean4 implements Inter {}
static class Bean5 {
    @Getter
    @Autowired
    @Resource(name = "bean3")
    private Inter bean4;
}

static class Config2{
    @Bean
    public Bean3 bean3(){
        return new Bean3();
    }

    @Bean
    public Bean4 bean4(){
        return new Bean4();
    }

    @Bean
    public Bean5 bean5(){
        return new Bean5();
    }
}

Bean5中依赖注入了一个Inter类型的 Bean,如果@Autowired注解生效,将会注入bean4,如果@Resource注解生效,将会注入 bean3。实际执行会看到注入的是bean4,也就是@Autowired注解生效。

实际上这取决于为工厂添加 Bean 后处理器时的顺序:

beanPostProcessorMap.forEach((k, v) -> {
    System.out.println("添加 Bean 后处理器:" + v);
    beanFactory.addBeanPostProcessor(v);
});

在输出中可以看到AutowiredAnnotationBeanPostProcessor比CommonAnnotationBeanPostProcessor先添加,这符合之前分析的结果。

事实上 Spring 提供一个默认的 Bean 后处理器排序依据:

image-20250619170605103

在调用AnnotationConfigUtils.registerAnnotationConfigProcessors时就会被设置。

因此我们只需要按照这个默认排序规则添加 Bean 后处理器:

// 为工厂添加 Bean 的后处理器
Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class);
beanPostProcessorMap.values().stream()
    .sorted(Objects.requireNonNull(beanFactory.getDependencyComparator()))
    .forEach((v) -> {
        System.out.println("添加 Bean 后处理器:" + v);
        beanFactory.addBeanPostProcessor(v);
    });

实际上默认的排序规则是按照Ordered接口的getOrder方法返回值确定先后顺序,值越小越优先。可以通过以下方式打印order:

if (v instanceof Ordered){
    Ordered ordered = (Ordered) v;
    int order = ordered.getOrder();
    System.out.println(order);
}

立即实例化

默认情况下,只有在通过getBean获取实例时,Bean 工厂才会创建并初始化 Bean 实例(如果不存在),一般的,出于性能考虑,我们会希望在容器准备就绪后立即加载所有的单例 Bean 到内存。这可以通过以下调用实现:

// 工厂准备好后立即初始化单例 bean
beanFactory.preInstantiateSingletons();
System.out.println("获取 Bean 实例");
System.out.println(beanFactory.getBean(Bean5.class).getBean4());

总结

通过以上尝试,我们看到 BeanFactory 的实现具有一定局限性,它不包含以下功能:

  • 添加并调用 Bean 工厂的后处理器。

  • 添加 Bean 的后处理器。

  • 立即实例化单例 bean。

这些功能都在ApplicationContext的实现类中完善。因此,通常我们接触和使用的都是后者。

ApplicatoinContext 的四种实现

ClassPathXmlApplicationContext

使用ClassPathXmlApplicationContext可以从类路径加载 XML 的方式创建容器:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Bean2 bean2 = context.getBean(Bean2.class);
System.out.println(bean2.getBean1());

Bean 定义由 XML 文件resources/applicationContext.xml描述:

<bean scope="singleton" name="bean1" class="com.example.demo.ApplicationContextTests$Bean1"/>
<bean scope="singleton" name="bean2" class="com.example.demo.ApplicationContextTests$Bean2">
    <property name="bean1" ref="bean1"/>
</bean>

对应的类定义:

static class Bean1{}
@Getter
@Setter
static class Bean2{
    private Bean1 bean1;
}

FileSystemXmlApplicationContext

和ClassPathXmlApplicationContext类似,ClassPathXmlApplicationContext同样是从指定的 XML 文件加载 Bean 定义。区别是后者指定的是文件系统中的路径,而非类路径。

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\workspace\\learn-spring-source\\ch1\\demo\\src\\test\\resources\\applicationContext.xml");
Bean2 bean2 = context.getBean(Bean2.class);
System.out.println(bean2.getBean1());

AnnotationConfigApplicationContext

AnnotationConfigApplicationContext是我们所熟悉的,通过注解的方式加载 Bean 定义:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Bean4 bean4 = context.getBean(Bean4.class);
System.out.println(bean4.getBean3());

对应的类定义:

static class Bean3{}

@AllArgsConstructor
static class Bean4{
    @Getter
    private Bean3 bean3;
}

@Configuration
static class Config{
    @Bean
    public Bean3 bean3(){
        return new Bean3();
    }

    @Bean
    public Bean4 bean4(){
        return new Bean4(bean3());
    }
}

AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext同样是以注解的方式添加 Bean 定义,不同的是它还会启动一个 Web 应用。

需要有spring-boot-starter-web依赖。

配置并启动 Web 应用:

AnnotationConfigServletWebServerApplicationContext context =
    new AnnotationConfigServletWebServerApplicationContext();
context.register(WebConfig.class);
context.refresh();
new CountDownLatch(1).await();

主线程需要使用new CountDownLatch(1).await()阻止线程结束。

配置类:

@Configuration
static class WebConfig{
    @Bean
    public ServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory(8081);
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(){
        return new DispatcherServletRegistrationBean(dispatcherServlet(), "/*");
    }

    @Bean("/hello")
    public Controller controller(){
        return (request, response) -> {
            response.getWriter().print("Hello World");
            return null;
        };
    }
}

访问页面 http://localhost:8081/hello 就能看到输出。

本文的完整示例代码可以从这里获取。

The End.

参考资料

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring spring boot
最后更新:2025年6月19日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号