Bean 定义和 Bean 实例
AnnotationConfigApplicationContext
首先,创建一个最简单的 Spring Boot 应用。
SpringApplication.run
的返回值:
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 {
@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);
}
}
可以看到结果中包含了通过 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));
}
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 后处理器排序依据:
在调用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.
文章评论