3.1 IoC容器简介
IoC容器是一个管理Bean的容器,在Spring的定义中,所有IoC容器都需要实现接口BeanFactory,它是一个顶级容器接口。为了增加对它的理解,我们首先阅读其源码,并讨论几个重要的方法,其源码如代码清单3-1所示。
代码清单3-1 BeanFactory接口源码
package org.springframework.beans.factory;
/**** imports ****/
public interface BeanFactory {
// 前缀
String FACTORY_BEAN_PREFIX = "&";
// 多个getBean()方法
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 两个获取Bean的提供器
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 是否包含Bean
boolean containsBean(String name);
// Bean是否为单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// Bean是否为原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 是否类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch)
throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch)
throws NoSuchBeanDefinitionException;
// 获取Bean的类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
Class<?> getType(String name, boolean allowFactoryBeanInit)
throws NoSuchBeanDefinitionException;
// 获取Bean的别名
String[] getAliases(String name);
}
上述源码中加入了中文注释,通过这些中文注释就可以理解这些方法的含义了,下面我们再介绍一些重要的方法。
● getBean():这是IoC容器最重要的方法之一,它的作用是从IoC容器中获取Bean。从多个getBean()方法中可以看到,有按名称(by name)获取Bean的,也有按类型(by type)获取Bean的,这就意味着在IoC容器中,允许我们按名称或者类型获取Bean,这对理解3.3节将讲到的Spring的依赖注入(dependency injection,DI)是十分重要的。
● isSingleton():判断Bean是否在IoC容器中为单例。这里需要记住的是,在IoC容器中, Bean默认都是以单例存在的,也就是使用getBean()方法根据名称或者类型获取的对象,在默认的情况下,返回的都是同一个对象。
● isPrototype():与isSingleton()方法是相反的,如果它返回的是true,那么当我们使用getBean()方法获取Bean的时候,IoC容器就会创建一个新的Bean返回给调用者,这些与3.7节将讨论的Bean的作用域相关。
由于BeanFactory接口定义的功能还不够强大,因此Spring在BeanFactory的基础上,还设计了一个更为高级的接口ApplicationContext,它是BeanFactory的子接口之一。在Spring的体系中,BeanFactory和ApplicationContext是最为重要的接口设计,在现实中我们使用的大部分IoC容器是ApplicationContext接口的实现类。BeanFactory和ApplicationContext的关系如图3-1所示。
图3-1 IoC容器的接口设计
在图3-1中可以看到,ApplicationContext接口通过扩展上级接口,进而扩展了BeanFactory接口,但是在BeanFactory的基础上,扩展了消息国际化接口(MessageSource)、环境可配置化接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublisher)和资源模式解析器接口(ResourcePatternResolver),所以ApplicationContext的功能会更为强大。
Spring Boot主要通过注解来将Bean装配到IoC容器中,为了贴近Spring Boot的需要,这里不再介绍与XML相关的IoC容器,而主要介绍一个基于注解的IoC容器——AnnotationConfigApplicationContext。从这个类的名称就可以看出,它是一个基于注解的IoC容器,之所以研究它,是因为Spring Boot装配和获取Bean的方式如出一辙。
下面来看一个最为简单的例子。首先定义一个Java简单对象(plain ordinary Java object,POJO)文件User.java,如代码清单3-2所示。
代码清单3-2 User.java
package com.learn.chapter3.pojo;
/**** imports ****/
public class User {
private Long id; // 编号
private String userName; // 用户名
private String note; // 备注
/**setters and getters **/
}
然后定义一个Java配置文件AppConfig.java,如代码清单3-3所示。
代码清单3-3 定义Java配置文件
package com.learn.chapter3.config;
/**** imports ****/
// 标注为Java配置类
@Configuration
public class AppConfig {
// @Bean表示将initUser()方法返回的对象装配到IoC容器中,该方法的属性name表示Bean的名称
@Bean(name = "user")
public User initUser() {
var user = new User();
user.setId(1L);
user.setUserName("user_name_1");
user.setNote("note_1");
return user;
}
}
这里需要注意加粗的注解。@Configuration表示这是一个Java配置类,Spring的容器会根据它来生成IoC容器,从而去装配Bean;@Bean表示将initUser()方法返回的对象装配到IoC容器中,该方法的属性name表示这个Bean的名称,如果没有配置它,则将方法名称initUser作为Bean的名称保存到IoC容器中。
做好了这些,就可以使用AnnotationConfigApplicationContext来构建自己的IoC容器了,如代码清单3-4所示。
代码清单3-4 使用AnnotationConfigApplicationContext
package com.learn.chapter3.main;
/**** imports ****/
public class IoCTest {
public static void main(String[] args) {
// 使用配置文件AppConfig.java创建IoC容器
var ctx = new AnnotationConfigApplicationContext(AppConfig.class);
try {
// 通过getBean()方法获取Bean
var user = ctx.getBean(User.class);
System.out.println(user.getUserName());
} finally {
// 关闭IoC容器
ctx.close();
}
}
}
上述代码将Java配置文件AppConfig.java传递给AnnotationConfigApplicationContext的构造方法,这样就能创建IoC容器了。IoC容器会根据AppConfig创建Bean,然后将Bean装配进来,这样就可以使用getBean()方法获取对应的Bean了。注意,Spring在默认的情况下,扫描到Bean后就创建Bean并将Bean装配到IoC容器中,而不是使用getBean()方法后才创建和装配Bean。运行代码后,可以打印出下面的日志:
......
16:11:04.110 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory –
Creating shared instance of singleton bean 'appConfig'
16:11:04.115 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory –
Creating shared instance of singleton bean 'user'
user_name_1
从日志中可以看到,配置文件中的名称为user的Bean已经被装配到IoC容器中,我们可以通过getBean()方法获取对应的Bean,并将Bean的属性信息输出出来。这个例子比较简单,注解@Bean也不是唯一装配Bean的方法,还有其他的方法可以让IoC容器装配Bean,Bean之间的依赖关系也需要进一步处理,这就是本章后面的主要内容了。