第一讲-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 对象等等。表面上看上去只有 bean 的一些基本操作,实际上 BeanFactory 的实现类实现了 Bean 的生命周期管理,控制反转,依赖注入等等。

BeanFactory 其中一个的实现类就是 DefaultListableBeanFactory, 我们可以看一下这个类的关系图:

image-20240719195953596

接下来我们看一下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]

3.5 处理环境信息的能力

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

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

运行结果如下:

D:\development_tools\jdk17\jdk-17.0.10
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 @   LilyFlower  阅读(7)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示