IOC原理简介

关于 IOC,如果只介绍理论知识还是比较难理解的,因此,本小节先展示一个 IOC 小案例,读者可通过案例理解在 Spring Boot 中如何使用 IOC。然后,通过介绍 IOC 容器,说明 IOC 内部的原理。

IOC小案例

为了方便编写代码,先看一下实例结构,如图 4.1 所示。

image 2024 03 31 14 12 57 252
Figure 1. 图4.1 实例结构

首先,新建 ioc 包,用于存放 IOC 中的程序(ioc 作为包时,应小写,其他包也是如此)。然后新建 pojo 包,用于存放 Bean 对象。这里新建 Student 类,Student.java 的代码如下所示。

package com.springBoot.ioc.pojo;
/**
  * @Desciption 一个简单的Bean对象
  */
public class Student {
   //三个基本属性
   private Long id;
   private String username;
   private String password;
   //generate setters and getters
   //……//
}

然后,再新建配置文件 config 包,用于存放配置文件。在这里新建 MySpringBootConfig 类,代码如下所示。

package com.springBoot.ioc.springBoot.ioc.config;
/**
  * @Desciption 配置文件
  */
@Configuration
public class MySpringBootConfig {
   @Bean(name="student")
   public Student getStu(){
      Student student=new Student();
      student.setId(1L);
      student.setUsername("Tom");
      student.setPassword("123456");
      return student;
   }
}

@Configuration 注解用来说明这是一个配置文件,Spring Boot 会根据这个注解生成 IOC 容器,以便装配 Bean@Bean 注解用来装配 Bean,在注解下方生成的 Bean 会被装配到 IOC 容器中。在上面的代码中,有一个 name 属性,返回 Beanidstudent,如果不写 name 属性,会把方法的名称作为 Beanid 放入 IOC 容器中。在编写代码的过程中,建议读者写 name 属性。

最后,进行测试。新建 test 包,用于存放测试类。IocTestDemo 代码如下所示。

package com.springBoot.ioc.test;
/**
  * @Desciption IOC实例测试类
  */
public class IocTestDemo {
      private static final Logger log=LoggerFactory. getLogger(IocTestDemo.class);

   public static void main(String[] args) {
      ApplicationContext context=new AnnotationConfigApplicationContext(MySpringBootConfig.class);
      Student student=context.getBean(Student.class);
      log.info("id= "+(student.getId()+""));
      log.info("username= "+student.getUsername());
      log.info("password= "+student.getPassword());
   }
}

从上面的代码中可以看到,测试类通过 Annotation Config App lication Context 构建 IOC 容器,因为这样可以读取配置文件 MySpringBootConfig。有时使用输出的方式效果不够好,所以可以通过日志来检查效果,如下所示。

23:16:25.597[main]DEBUGorg.springframework.beans.factory.support. DefaultListableBeanFactory - Returning cached instance of singleton bean 'student'
23:16:25.597 [main] INFO com.springBoot.ioc.config.IocTestDemo - id= 1
23:16:25.597 [main] INFO com.springBoot.ioc.config.IocTestDemo username= Tom
23:16:25.597 [main] INFO com.springBoot.ioc.config.IocTestDemo password= 123456

这里是最后显示的日志,前面还有很多 Bean 生成的日志,这里先不说明,后面会进一步说明。在此处的日志上,我们可以看到 student 被获取到,然后返回 student 的具体属性内容。

IOC简介

IOC 简介中主要介绍 IOC 中的顶级接口 BeanFactory,然后再介绍 ApplicationContext 接口,最后说明 Spring Boot 中使用的 IOC 实现类 AnnotationConfigApplicationContext

BeanFactory接口

Spring Boot 中有一个顶级接口 BeanFactory,而我们知道 IOC 主要用来管理 Bean,所以所有的 IOC 容器都需要实现这个接口,才具有容器的基本功能。为了后面更容易说明原理,我们先看一下这个接口的基本用法,如下所示。

package org.springframework.beans.factory;
/**
*接口BeanFactory
*/
public interface BeanFactory {
   //用&符号获取BeanFactory本身,用来区分通过容器获取FactoryBean产生的对象和FactoryBean本身
   String FACTORY_BEAN_PREFIX = "&";
   /**
      * 使用不同的Bean检索方法,从IOC容器中得到所需要的Bean,从而忽略具体的IOC实现
      */
   Object getBean(String name) throws BeansException;
   <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
      Object getBean(String name, Object... args) throws BeansException;
   <T> T getBean(Class<T> requiredType) throws BeansException;
   <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
   //是否包含bean
   boolean containsBean(String name);
   //指定名字的Bean是否是Singleton类型
   boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
   //指定名字的Bean是否是Prototype类型
   boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
   //查询指定名字的Bean的Class类型是否是特定的Class类型
   boolean isTypeMatch(String name, ResolvableType typeToMatch)
    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch)
   //查询指定名字的Bean的Class类型
   Class<?> getType(String name) throws NoSuchBeanDefinitionException;
   //查询指定名字的Bean的所有别名
   String[] getAliases(String name);
}

在代码中,我们可以看到重点的部分是多个 getBean 方法,使用不同的方法获取 Bean,例如使用 Bean 名称。Bean 类型也是比较常用的方式。在接口中,还有两个判断 Bean 类型的函数,如果 BeanSingleton,在获取 Bean 时,就都是同一个 Bean 对象。在默认情况下,Bean 都是 Singleton,即单例;而 Prototype 类型的 Bean 则相反,每次取 Bean 对象都会创建新的对象。

在代码第 8 行有个常量 FACTORY_BEAN_PREFIX,用 & 符号获取 BeanFactory 本身,可用来区分通过容器获取 FactoryBean 产生的对象和 FactoryBean 本身。

高级形态的IOC容器ApplicationContext

在上文介绍了顶级接口,但是其功能还不够强大,所以继续介绍新的接口 Application Context。它是应用上下文接口,不仅可细化 BeanFactory 接口,还可扩展 Application EventPublisherMessageSourceResourcePatternResolver 等接口,使 IOC 容器的支持更加高级,是高级形态的 IOC 容器。

其中 ApplicationEventPublisher 是应用事件发布接口,ResourcePatternResolver 是资源可配置接口,MessageSource 是消息国际化接口,Application Context 类关系如图 4.2 所示。

image 2024 03 31 15 21 33 718
Figure 2. 图4.2 ApplicationContext类关系图

基于注解的IOC容器AnnotationConfigApplicationContext

在上文的实例中已经使用过这个类,用来构建 IOC 容器,读取配置文件。在 Spring Boot 中,主要通过注解的方式来装配 Bean 对象,避免加载 application.xml 文件,所以必须要讲解这个类。在讲解之前,先了解它们之间的关系。

建议使用 IDEA 生成这个关系图。首先进入 AnnotationConfigApplicationContext 中,然后单击右键,选择 show Diagrams。关系图如图 4.3 所示(仅显示需要内容)。

image 2024 03 31 15 22 45 063
Figure 3. 图4.3 AnnotationConfigApplicationContext类关系图

从图 4.3 中可以发现左侧仍是 ApplicationContext 的接口关系图,右侧才是要介绍的。

  • GenericApplicationContext:在图的右下方,是通用应用上下文类。在这里需要说到父类,就是 AbstractApplicationContext。因为在设计它时,使用了模板方法设计模式,对于某些方法需要在具体的子类中实现。GenericApplicationContext 持有一个单例的固定 DefaultListableBeanFactory 实例,在创建 GenericApplicationContext 实例时就会创建 DefaultListableBeanFactory 实例。在访问 Bean 前,DefaultListableBeanFactory 先注册所有的 definition

  • AnnotationConfigRegistry:在图 4.3 的右下方,是注解配置注册表。用于注解配置应用上下文的通用接口,拥有一个注册配置类和一个扫描配置类。

  • AbstractApplicationContext:在 GenericApplicationContext 中有一些解释。

  • BeanDefinitionRegistry:持有 ×××BeanDefinition 实例的 Bean definitions 的注册表接口。DefaultListableBeanFactory 实现了这个接口,所以可以向 BeanFactory 里面注册 Bean。又因为 GenericApplicationContext 内置一个 DefaultListableBeanFactory 实例,所以它对这个接口的实现实际上是通过调用这个实例的相应方法实现。