3. IOC 容器概述

87 阅读9分钟

3. IOC 容器概述

Spring 有 3 大基石:

  1. BeanDefinition,bean 的元信息
  2. BeanFactory,用来管理 BeanDefinition
  3. BeanDefinitionRegistry, BeanDefinition 的注册接口
  4. PostProcessor,用于操作 Spring

3.1 依赖查找

  • 根据 Bean 名称查找
    • 实时查找
    • 延迟查找
  • 根据 Bean 类型查找
    • 单个 Bean 对象
    • 所有 Bean 对象
  • 根据 Bean 名称 + 类型查找
  • 根据 Java 注解查找
    • 单个 Bean 对象
    • 所有 Bean 对象
  1. 根据 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'}
  1. 根据 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 延迟查找

  1. 根据 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'}
  1. 根据 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'}}
  1. 根据注解查找,自定义一个注解 @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 对象
  • 注入类型
    • 实时注入
    • 延迟注入
  1. 根据 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'}]

所以这和依赖查找到底有什么区别?

  1. 根据 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 对象
  • 容器内建 依赖
  1. 容器内建 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'}]}
  1. 容器内建依赖,如 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;
  1. 使用 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='杭州'}}
  1. 使用 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 容器生命周期

  • 启动
  • 运行
  • 停止
  1. 启动 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();
        }
    }
}
  1. 停止 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 面试题

  1. 什么是 Spring IoC 容器?

    IoC 容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的 Component。容器通过读取提供的 bean 的配置来进行对象实例化,配置和组装。bean 配置数据可以通过 XML,Java 注解或 Java 代码提供。

    IoC 就是控制反转,有两种实现方式 依赖注入DI 和依赖查找 DL,经典实现有 BeanFactory 和 ApplicationContext

  2. BeanFactory 与 FactoryBean?

    BeanFactory 是 IoC 底层容器,规定了根据 Bean 名称,Bean 类型依赖查找的方法,常见的子接口有 ListableBeanFactory,HierarchicalBeanFactory。

    FactoryBean 是创建 Bean 的一种方式,帮助其实现复杂的初始化逻辑。

    FactoryBean 即是一个工厂,又是一个 Bean,是工厂,是因为其getObject()方法负责生产对象,是 Bean,是因为其实现类需要配置为 Bean 并将其加载到 IOC 容器。

    参考 4.5 FactoryBean 实例化 Bean

  3. Spring IoC 容器启动时做了哪些准备?

    IoC 配置元信息读取和解析,IoC 容器声明周期,Spring 事件发布,国际化等,具体查看后续章节