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
是一个接口,查看其继承关系:
比较重要的父接口有ApplicationContext
和BeanFactory
。
打上断点,启动调试模式,可以看到实际运行时使用的真实类型:
查看AnnotationConfigApplicationContext
的继承关系比较复杂,最值得注意的:
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));
}
可以看到,即使没有任何自定义的 Bean,默认情况下 Spring 框架也会注册很多必要的 Bean,甚至包含了AnnotationConfigApplicationContext
容器自己。
如果添加了自定义 Bean,在这里也会看到。
DefaultSingletonBeanRegistry
查看DefaultListableBeanFactory
的继承关系:
它有一个基类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 对象:
小结
从上面的分析不难看出,AnnotationConfigApplicationContext
的设计采用了代理(委托)模式,它包含一个 DefaultListableBeanFactory
类型的 BeanFactory
,具体的 Bean 定义和 Bean 实例都保存在其中,而AnnotationConfigApplicationContext
本身所有对 Bean 的注册、获取等操作都代理给DefaultListableBeanFactory
的对应方法实现。而DefaultListableBeanFactory
和AnnotationConfigApplicationContext
都实现了基本的BeanFactory
接口,这也正是代理模式的基础。
其它功能
上面介绍了 Bean 工厂的核心功能——维护 Bean 定义和 Bean 实例。事实上 ApplicationContext
除了继承 BeanFactory 的相关接口,还继承了其它的几个接口:
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);}
}
可以看到结果中包含了通过 Maven 导入的 jar 包。
MessageSource
MessageSource 可以实现国际化。
添加国际化相关资源文件:
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底层原理