3. IOC 容器概述
Spring 有 3 大基石:
- BeanDefinition,bean 的元信息
- BeanFactory,用来管理 BeanDefinition
- BeanDefinitionRegistry, BeanDefinition 的注册接口
- PostProcessor,用于操作 Spring
3.1 依赖查找
- 根据 Bean 名称查找
- 实时查找
- 延迟查找
- 根据 Bean 类型查找
- 单个 Bean 对象
- 所有 Bean 对象
- 根据 Bean 名称 + 类型查找
- 根据 Java 注解查找
- 单个 Bean 对象
- 所有 Bean 对象
- 根据 Bean 名称实时查找
public class User {
private Long id;
private String name;
// 省略 setter getter
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
在 spring 配置文件中配置 bean,设置 id 为 “user”,查找时使用,设置属性值
dependcy-lookup-context.xml
<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
使用beanFactory.getBean("user")从容器中查找 id="user" 的 bean
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-lookup-context.xml");
// 3. 使用ioc容器api查找bean
User user = (User) beanFactory.getBean("user");
System.out.println("实时查找: " + user);
}
输出结果:
实时查找: User{id=1, name='tracccer'}
- 根据 Bean 名称延迟查找,ObjectFactoryCreatingFactoryBean 可以将查找的 bean 保存到自己的
targetBeanName属性中
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<!-- 关联到user bean -->
<property name="targetBeanName" value="user"/>
</bean>
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-lookup-context.xml");
// 3. 使用ioc容器api查找bean
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
System.out.println("延迟查找: " + user);
}
输出结果:
延迟查找: User{id=1, name='tracccer'}
// 补充:ObjectFactory 延迟查找
- 根据 Bean 的类型查找单个对象
dependcy-lookup-context.xml
<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
如果配置文件中定义了多个该类型的 bean,则下面代码会报错 NoUniqueBeanDefinitionException
private static void lookupByType(BeanFactory beanFactory) {
// 3. 使用ioc容器api查找bean, 根据类型 User.class 查找
User user = beanFactory.getBean(User.class);
System.out.println("类型查找: " + user);
}
输出结果:
类型查找: User{id=1, name='tracccer'}
- 根据 Bean 的类型查找所有该类型的对象,在 xml 文件中配置两个 User 类型的 bean
<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
<bean id="user2" class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
使用listableBeanFactory.getBeansOfType(User.class)查找所有的 User 对象,查找结果是一个 Map<String, User>,key 是 bean 的 id,value 是实际的 user 对象
private static void lookupAllByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class); // 根据类型查找
System.out.println("查找到的所有 User 对象: " + users);
}
}
输出结果:
查找到的所有 User 对象:
{user=User{id=1, name='tracccer'}, user2=User{id=1, name='tracccer'}}
- 根据注解查找,自定义一个注解 @Super
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Super {
String value() default "";
}
@Super
public class SuperUser extends User {
private String address;
// 省略 setter/getter
@Override
public String toString() {
return "User{" +
"id=" + getId() +
", name='" + getName() +
", address='" + address + '\'' +
'}';
}
}
配置 SuperUser bean,并设置属性 address,设置父对象为前面设置的 user bean,会继承该 user bean 的属性值
<bean id="superUser" class="org.geekbang.ioc.overview.lookup.domain.SuperUser" parent="user">
<property name="address" value="杭州"/>
</bean>
使用listableBeanFactory.getBeansWithAnnotation(Super.class)查找所有被注解 @Super 标记的类的 bean
private static void lookupByAnnotation(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
// 根据注解查找
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
System.out.println("查找标注@Super User 对象: " + users);
}
}
输出结果:
查找标注@Super User 对象:
{superUser=User{id=1, name='tracccer, address='杭州'}}
如果使用第 4 步根据 Bean 的类型查找所有该类型的对象,会查找到两个 User 对象,分别是 user 和 superUser bean,使用第 3 步根据 Bean 的类型查找单个对象,会报错NoUniqueBeanDefinitionException,可以通过为 bean 配置primary属性解决。
<bean id="superUser" class="org.geekbang.ioc...SuperUser" parent="user"
primary="true"> primary 属性表示根据类型查找时优先返回
<property name="address" value="杭州"/>
</bean>
3.2 依赖注入
- 根据 Bean 名称注入
- 根据 Bean 类型注入
- 单个 Bean 对象
- 所有该类型的 Bean 对象
- 注入容器内建 Bean 对象
- 注入非 Bean 对象
- 注入类型
- 实时注入
- 延迟注入
- 根据 Bean 的名称注入
<bean id="userRepository" class="org.geekbang.ioc.overview.injection.repository.UserRepository">
<property name="users">
<util:list>
<ref bean="superUser"/>
<ref bean="user"/>
</util:list>
</property>
</bean>
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-injection-context.xml");
// 依赖查找userRepository
UserRepository userRepository = beanFactory.getBean("userRepository", UserRepository.class);
// 查看根据名称是否注入成功
System.out.println(userRepository.getUsers());
}
输出结果:
[User{id=1, name='tracccer, address='杭州'}, User{id=1, name='tracccer'}]
所以这和依赖查找到底有什么区别?
- 根据 Bean 的类型注入
<bean id="userRepository" class="org.geekbang.ioc.overview.injection.repository.UserRepository"
autowire="byType"> <!-- Auto-Wiring 根据类型自动绑定 -->
<!-- 不需要手动配置, 可以绑定所有 User 类型的 bean -->
</bean>
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-injection-context.xml");
// 依赖查找userRepository
UserRepository userRepository = beanFactory.getBean("userRepository", UserRepository.class);
// 查看根据类型是否自动注入成功
System.out.println(userRepository.getUsers());
}
输出结果:
[User{id=1, name='tracccer'}, User{id=1, name='tracccer, address='杭州'}]
// 补充:各种依赖注入类型的代码示例,自动绑定与依赖注入的关系,结合第 6 章
3.3 依赖来源
- 自定义 bean
- 容器内建 bean 对象
- 容器内建 依赖
- 容器内建 bean 对象,如 Environment 对象
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-injection-context.xml");
// 获取容器内建bean Environment
Environment environment = beanFactory.getBean(Environment.class);
System.out.println("容器 Environment 类型的内建 bean: "+environment);
}
容器 Environment 类型的内建 bean: StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}]}
- 容器内建依赖,如 BeanFactory
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-injection-context.xml");
// 获取beanFactory, 依赖注入内建依赖
System.out.println("beanFactory: " + userRepository.getBeanFactory()); // 依赖注入
System.out.println(beanFactory == userRepository.getBeanFactory()); // false
}
3.4 配置元信息
- bean 定义配置
- xml 文件
- java 注解
- java api
- properties 文件
- IOC 容器设置
- xml 文件
- java 注解
- java api
- 外部化属性配置
- java 注解
3.5 IOC 容器
BeanFactory 和 ApplicationContext 谁才是 IOC 容器?
BeanFactory 是一个底层的 IOC 容器,ApplicationContext 是 BeanFactory 的子接口,提供更多特性,包括 AOP,国际化,事件发布,web 支持等
private static void whoIsIOCContainer(UserRepository userRepository, BeanFactory beanFactory) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("dependcy-injection-context.xml");
// 依赖查找获取自定义bean
UserRepository userRepository = beanFactory.getBean("userRepository", UserRepository.class);
// 获取beanFactory, 依赖注入内建依赖
System.out.println("beanFactory: " + userRepository.getBeanFactory()); // 依赖注入
// 这个表达式为什么为 false?
System.out.println(beanFactory == userRepository.getBeanFactory()); // false
AbstractRefreshableApplicationContext applicationContext = (AbstractRefreshableApplicationContext) beanFactory;
// 打印 ClassPathXmlApplicationContext 的 beanFactory 属性
System.out.println("beanFactory: " + applicationContext.getBeanFactory());
// 判断ClassPathXmlApplicationContext 的 beanFactory 属性是否与依赖注入的 beanFactory 相等
System.out.println(applicationContext.getBeanFactory() == userRepository.getBeanFactory()); // true
}
输出结果:
beanFactory: org...DefaultListableBeanFactory@37918c79: defining beans [user,objectFactory,superUser,userRepository]; root of factory hierarchy
false
beanFactory: org...DefaultListableBeanFactory@37918c79: defining beans [user,objectFactory,superUser,userRepository]; root of factory hierarchy
true
- userRepository.getBeanFactory() 返回的是对象 DefaultListableBeanFactory@1719
- beanFactory 其实是 ClassPathXmlApplicationContext, 他的属性 beanFactory 保存了对象 DefaultListableBeanFactory@1719
- 即 ClassPathXmlApplicationContext 是使用组合的方式来扩展 DefaultListableBeanFactory 的,
- 前者返回 DefaultListableBeanFactory 类型的对象, 后者返回的是 ClassPathXmlApplicationContext 对象, 故返回false
- ClassPathXmlApplicationContext 是 AbstractRefreshableApplicationContext的子类,属性中也保存了 DefaultListableBeanFactory
继承关系: ClassPathXmlApplicationContext->AbstractXmlApplicationContext->AbstractRefreshableConfigApplicationContext->AbstractRefreshableApplicationContext
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
// 该属性保存 beanFactory
private DefaultListableBeanFactory beanFactory;
3.6 应用上下文
ApplicationContext 除了 IOC 容器角色,还提供这些特性,这也是其与 BeanFactory 的不同之处:
- 面向切面 AOP
- 配置元信息 Configuration Metadata
- 资源管理 Resources
- 事件 Events
- 国际化 i18n
- 注解 Annotations
- Environment 抽象 配置 profile
3.7 使用 IOC 容器
通过 3.5 章节知道了 ApplicationContext 是 BeanFactory 子接口,但是 AnnotationConfigApplicationContext 和 ClassPathXmlApplicationContext 都是通过组合的方式来扩展 DefaultListableBeanFactory 的。
// AnnotationConfigApplicationContext 继承了 GenericApplicationContext
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
// GenericApplicationContext 通过组合的方式扩展 DefaultListableBeanFactory
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
// 该属性保存 beanFactory
private final DefaultListableBeanFactory beanFactory;
- 使用 BeanFactory 作为 IoC 容器,DefaultListableBeanFactory
public static void main(String[] args) {
// 1.创建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// bean 配置文件路径
String location = "dependcy-lookup-context.xml";
// 2.加载xml配置文件
int count = reader.loadBeanDefinitions(location);
// 返回 3, 与xml中定义 bean 的数量一致
System.out.println("容器中bean的数量: " + count);
// 3.依赖查找, 根据名称
User user = beanFactory.getBean("user", User.class);
System.out.println(user);
// 4.依赖查找所有 User 对象, 根据类型
lookupAllByType(beanFactory);
// 5.停止应用上下文
applicationContext.close();
}
输出结果:
容器中bean的数量: 3
User{id=1, name='tracccer'}
查找到的所有 User 对象: {user=User{id=1, name='tracccer'}, superUser=User{id=1, name='tracccer, address='杭州'}}
- 使用 ApplicationContext 作为 IoC 容器,AnnotationConfigApplicationContext 注解类型
public static void main(String[] args) {
// 1.创建 ApplicationContext 容器, 使用注解配置
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2.注册主类到容器
applicationContext.register(AnnotationApplicationContextAsIoCContainerDemo.class);
// 3.启动应用上下文
// 为什么 ClassPathXmlApplicationContext 不用启动? 因为在构造方法中已经调用refresh启动了
applicationContext.refresh();
// 4.依赖查找, 根据名称
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
// 4.依赖查找所有 User 对象, 根据类型
lookupAllByType(applicationContext);
}
// 通过 java 注解的方式, 注册一个 bean 到 ioc 容器
@Bean
public User user() {
User user = new User();
user.setId(1L);
user.setName("小毛");
return user;
}
输出结果:
User{id=1, name='小毛'}
查找到的所有 User 对象: {user=User{id=1, name='小毛'}}
3.8 IOC 容器生命周期
- 启动
- 运行
- 停止
- 启动 refresh
AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
- 停止 close
protected void doClose() {
// Check whether an actual close attempt is necessary...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Switch to inactive.
this.active.set(false);
}
}
3.9 面试题
-
什么是 Spring IoC 容器?
IoC 容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的 Component。容器通过读取提供的 bean 的配置来进行对象实例化,配置和组装。bean 配置数据可以通过 XML,Java 注解或 Java 代码提供。
IoC 就是控制反转,有两种实现方式 依赖注入DI 和依赖查找 DL,经典实现有 BeanFactory 和 ApplicationContext
-
BeanFactory 与 FactoryBean?
BeanFactory 是 IoC 底层容器,规定了根据 Bean 名称,Bean 类型依赖查找的方法,常见的子接口有 ListableBeanFactory,HierarchicalBeanFactory。
FactoryBean 是创建 Bean 的一种方式,帮助其实现复杂的初始化逻辑。
FactoryBean 即是一个工厂,又是一个 Bean,是工厂,是因为其
getObject()方法负责生产对象,是 Bean,是因为其实现类需要配置为 Bean 并将其加载到 IOC 容器。参考 4.5 FactoryBean 实例化 Bean
-
Spring IoC 容器启动时做了哪些准备?
IoC 配置元信息读取和解析,IoC 容器声明周期,Spring 事件发布,国际化等,具体查看后续章节