这两个内容,大家在依赖查找和依赖注入的时候肯定已经非常熟悉了,不过我们简单回忆一下概念。

IOC 的两种实现方式:

  • 依赖查找 (DL):根据 name 或者 type 在 IOC 容器中获取组装好的 Bean。
  • 依赖注入 (DI):IOC 容器根据依赖关系找到了对应的 Bean,为其字段进行赋值,从而形成一个完整的 Bean。

    1. 我们是如何获取容器的

    下面这两段代码大家应该很熟悉,在 resources 目录下创建一个 bean.xml 文件,写入头部的约束,然你用 xml 的方式配置两个 bean(下面把第二个先注释掉了),我们分别可以通过下面这几种方式,根据 name、或者 type 的方式拿到这个 bean。 ```xml <?xml version=”1.0” encoding=”UTF-8”?>

  1. ```java
  2. @Test
  3. public void testGetBean(){
  4. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  5. // 指定类或者单个类的实现方式
  6. UserDao userDao1 = (UserDao) context.getBean("userDao");
  7. UserDao userDao2 = context.getBean(UserDao.class);
  8. UserDao userDao3 = context.getBean("userDao", UserDao.class);
  9. // 如果 userDao 接口有多个实现类
  10. Map<String, UserDao> beansOfTypeMap= context.getBeansOfType(UserDao.class);
  11. beansOfTypeMap.forEach((beanName, bean) -> {
  12. System.out.println(beanName + " : " + bean.toString());
  13. });
  14. }

2. 为什么用 ApplicationContext

如果你入门的时候看了一些书籍或者视频,大家应该会很熟悉,刚开始的时候大家往往会见到使用 BeanFactory 去承接这个对象。

BeanFactory factory = new ClassPathXmlApplicationContext("bean.xml");

这又是为什么呢?大家可以先试试,把 bean.xml 中 userDao 的两个实现类都放开,如果你想要一次性取出多个 bean,就会发现 BeanFactory 其实没有为我们提供很好的 api,这时大家把类切换为 ApplicationContext,就会发现,可以使用 getBeansOfType 这个方法。所以就这一点上看来,似乎 ApplicationContext 提供了更多的方法和功能。下面接着看看它们两者之间的关系。

3. ApplicationContext 和 BeanFactory

首先借助 IDEA 的分析,可以看到 ApplicationContext 是 BeanFactory 的子接口
image.png
下面看看官网的描述:
地址:https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#spring-core

The org.springframework.beans and org.springframework.context packages are the basis for Spring Framework’s IoC container. The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object. ApplicationContext is a sub-interface of BeanFactory. It adds:

  • Easier integration with Spring’s AOP features
  • Message resource handling (for use in internationalization)
  • Event publication
  • Application-layer specific contexts such as the WebApplicationContext for use in web applications.

In short, the BeanFactory provides the configuration framework and basic functionality, and the ApplicationContext adds more enterprise-specific functionality. The ApplicationContext is a complete superset of the BeanFactory and is used exclusively in this chapter in descriptions of Spring’s IoC container. For more information on using the BeanFactory instead of the ApplicationContext, see TheBeanFactory. In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

简单翻译一下这段话,就是:springframework.beans 和 springframework.context 这两个包就是 IOC 的基础。而 BeanFactory 就提供了一种高级配置的机制,能够用来管理各种类型的对象,而 ApplicationContext 是 BeanFactory 的一个子接口。

后面的一些举例就是提及了 ApplicationContext 的一些优点,例如更容易与 AOP 集成,还有消息资源处理(用于国际化)、事件发布、应用层特定上下文,例如 Web 应用程序中使用的 WebApplicationContext。

总而言之,言而总之, ApplicationContext 就是比 BeanFactory 强,它添加了更多的企业级功能,所以它是一个更加完整的 BeanFactory 的超集。

补充一个区别就是:两者创建对象的时间点不一样

  • ApplicationContext:单例对象适用采用此接口
    • 构建核心容器时,创建对象时采用立即加载的方式。即:只要一读取完配置文件马上就创建配置文件中配置的对象
  • BeanFactory:多例对象适合

    • 构建核心容器时,创建对象时采用延迟加载的方式。即:什么时候根据id获取对象,什么时候才真正的创建对象

      4. ApplicationContext 的一些用法

      4.1 实现类

      默认选择第一个就行了,其他几个都比较少见。
  • ClassPathXmlApplicationContext:可以加载类路径下的配置文件,当然配置文件必须在类路径下(用的更多)

  • FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(有磁盘访问权限)
  • AnnotationConfigApplicationContext:读取注解创建容器

    4.2 获取容器中所有 Bean

    前面我们通过 getBeansOfType 可以根据类型,一次性取出的多个 Bean,就例如 UserDao 接口有多个实现类。 ```java ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);

Map beansOfTypeMap= context.getBeansOfType(UserDao.class);

那么如果想要获取到容器中所有的 Bean,我们既不知道类型,也不知道名称,该怎么办呢?

使用 getBeanDefinitionNames 即可
```java
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

String[] beanDefinitionNames = context.getBeanDefinitionNames();
Stream.of(beanDefinitionNames).forEach(System.out::println);

4.3 根据注解获取 Bean

创建一个注解,并且加载 UserDaoImpl 这个实现类上,在 xml 中配置好

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnno {

}

这样就能获取到所有添加了这个注解的 Bean

Map<String, Object> beansWithAnnotationMap = context.getBeansWithAnnotation(TestAnno.class);
beansWithAnnotationMap.forEach((beanName, bean) -> {
    System.out.println(beanName + " : " + bean.toString());
});

4.4 延迟查找

这部分平时用的场景会比较少,但是也是一种学习哇。
例如我们找不到 UserDao 这个 Bean,可以直接 catch 住,然后 new 一个。

UserDao userDao;
try {
    userDao = ctx.getBean(UserDao.class);
} catch (NoSuchBeanDefinitionException e) {
    userDao = new UserDaoImpl();
}

但是这样工作量很大,可以考虑使用 ApplicationContaxt 的方法 containsBean 用于判断这个 bean 是否存在。

UserDao userDao = context.containsBean("userDao") ? (UserDao) ctx.getBean("userDao") : new UserDaoImpl();

后来发现了一个更好的方法,启动时不会报错,只有当真正的 bean 被获取的时候,如果没有,则会抛出 NoSuchBeanDefinitionException 异常,现在而言,其实错误的抛出时机已经被延迟了。

ObjectProvider<UserDao> userDaoProvider = context.getBeanProvider(UserDao.class);

不过,即使延迟了,还是会报错,所以还有办法改进。这个 provider 下还有一个方法 getIfAvailable 可以继续判断是否可用。如果没有则,再 new 出来。

UserDao userDao = userDaoProvider.getIfAvailable();
if (userDao == null) {
    userDao = new UserDao();
}

Spring 5.0 后有有了一些新的写法

UserDao userDao = userDaoProvider.getIfAvailable(() -> new UserDao());

UserDao userDao= userDaoProvider.getIfAvailable(UserDao::new);

还可以直接操作这个字段

// 直接用这个 bean -> userDao
dogProvider.ifAvailable(userDao -> System.out.println(userDao));