欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > Spring 源码学习 1:ApplicationContext

Spring 源码学习 1:ApplicationContext

2025/6/20 14:02:44 来源:https://blog.csdn.net/hy6533/article/details/148763499  浏览:    关键词:Spring 源码学习 1:ApplicationContext

Spring 源码学习 1:ApplicationContext

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

比较重要的父接口有ApplicationContextBeanFactory

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

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 的对应方法实现。而DefaultListableBeanFactoryAnnotationConfigApplicationContext 都实现了基本的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 {@Getterprivate final Long userId;public UserUpdatedEvent(Object source, Long userId) {super(source);this.userId = userId;}
}

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

@Component
public class UserListener {@EventListenerpublic 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));
}

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

The End.

参考资料

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词