Spring5_容器与Bean_容器接口与容器实现

651 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情


⭐️前面的话⭐️

✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《王道408》,📚《深入理解 Java 虚拟机-周志明》,📚《Java 核心技术卷》
💬算法刷题:✅力扣🌐牛客网
🎈Github
🎈码云Gitee


1容器接口

  • BeanFactory 接口,典型功能有:
    • getBean
  • ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能,如:
    • 国际化
    • 通配符方式获取一组 Resource 资源
    • 整合 Environment 环境(能通过它获取各种来源的配置信息)
    • 事件发布与监听,实现组件之间的解耦

1.1到底什么是 BeanFactory

  • 它是 ApplicationContext 的父接口
  • 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能(所谓组合,BeanFactoryApplicationContext 的一个成员变量)

1.2BeanFactory 能干点啥

  • 表面上只有 getBean
  • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能, 都由它的实现类提供(DefaultListableBeanFactory)
  • 通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);
    System.out.println(context);
    Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
    singletonObjects.setAccessible(true);
    
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
    
    map.entrySet().stream().filter(e -> e.getKey().startsWith("component"))
            .forEach(e -> {
                System.out.println(e.getKey() + "=" + e.getValue());
            });
}

// 注册的2个bean
@Component
public class Component1 {
    private static final Logger log = LoggerFactory.getLogger(Component1.class);
}

@Component
public class Component2 {
    private static final Logger log = LoggerFactory.getLogger(Component2.class);
}

打印结果

component1=com.itheima.a01.Component1@4d8539de
component2=com.itheima.a01.Component2@3eba57a7

1.3ApplicationContext 比 BeanFactory 多点啥(扩展的功能主要体现在继承的4个父接口上)⭐️

  • 多语言能力(国际化能力)
  • 通配符匹配资源的能力
  • 整合 Environment 环境
  • 发布事件(事件解耦)

多语言能力

System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));

通配符匹配资源的能力

Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
  	System.out.println(resource);
}

打印结果如下:

URL [jar:file:/Users/cat/environment/repo/org/springframework/boot/spring-boot/2.5.5/spring-boot-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/Users/cat/environment/repo/org/springframework/boot/spring-boot-autoconfigure/2.5.5/spring-boot-autoconfigure-2.5.5.jar!/META-INF/spring.factories]
URL [jar:file:/Users/cat/environment/repo/org/springframework/spring-beans/5.3.10/spring-beans-5.3.10.jar!/META-INF/spring.factories]

整合 Environment 环境

System.out.println(context.getEnvironment().getProperty("JAVA_8_HOME"));
System.out.println(context.getEnvironment().getProperty("JAVA_11_HOME"));
System.out.println(context.getEnvironment().getProperty("spring.messages.basename"));

发布事件(事件解耦)

总结一下:

a. BeanFactory 与 ApplicationContext 并不仅仅是简单接口继承的关系, ApplicationContext 组合并扩展了 BeanFactory 的功能

b. 又新学一种代码之间的解耦途径

2容器实现

Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供参考

  • DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现
  • ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
  • FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
  • XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
  • AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
  • AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
  • AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
  • AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)

要注意的是,后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来

2.1BeanFactory实现(了解DefaultListableBeanFactory就够了)

  • beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象
    • 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中
    • bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等
  • beanFactory 需要手动调用 beanFactory 后处理器对它做增强
    • 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition
  • beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强
    • 例如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的
    • bean 后处理的添加顺序会对解析结果有影响,见视频中同时加 @Autowired,@Resource 的例子
  • beanFactory 需要手动调用方法来初始化单例
  • beanFactory 需要额外设置才能解析 ${}#{}
public static void main(String[] args) {
  DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
  // step1:bean 的定义(class, scope, 初始化, 销毁)
  AbstractBeanDefinition beanDefinition =
    BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
  // step2:bean 的注册
  beanFactory.registerBeanDefinition("config", beanDefinition);


  // 给 BeanFactory 添加一些常用的后处理器(解析@Configuration、@Bean ,扩展了beanFactory)
  AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

  // 第一类处理器:BeanFactory 后处理器主要功能,补充了一些 bean 定义
  beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
    // 这里是执行bean工厂后处理器
    beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
  });

  //for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
  //    System.out.println(beanDefinitionName);
  //}

  System.out.println("---------------------> p9");
  //System.out.println(beanFactory.getBean(Bean1.class).getBean2());
  // null
  // 原因:@Autowired对于beanFactory也属于扩展功能

  // 第二类处理器:Bean 后处理器, 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource ...
  beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

  //for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
  //    System.out.println(beanDefinitionName);
  //}


  System.out.println("添加 Bean 后处理器后...");
  System.out.println(beanFactory.getBean(Bean1.class).getBean2());

  // 上面我们看到的bean都是延时加载的,需要用到时才创建
  // 我们可以预先实例化所有的单例对象
  beanFactory.preInstantiateSingletons(); // 准备好所有单例

}
@Configuration
static class Config {
    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }
    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
    @Bean
    public Bean3 bean3() {
        return new Bean3();
    }
    @Bean
    public Bean4 bean4() {
        return new Bean4();
    }
}

interface Inter {}
static class Bean3 implements Inter {
}
static class Bean4 implements Inter {
}

static class Bean1 {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {
        log.debug("构造 Bean1()");
    }

    @Autowired
    private Bean2 bean2;
    public Bean2 getBean2() {
        return bean2;
    }

    @Autowired
    @Resource(name = "bean4")
    private Inter bean3;
    public Inter getInter() {
        return bean3;
    }
}

static class Bean2 {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    public Bean2() {
        log.debug("构造 Bean2()");
    }
}

打印结果

---------------------> p9
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
bean3
bean4
添加 Bean 后处理器后...
23:03:28.128 [main] DEBUG com.itheima.a02.TestBeanFactory$Bean1-构造 Bean1()
23:03:28.142 [main] DEBUG com.itheima.a02.TestBeanFactory$Bean2 -构造 Bean2()
com.itheima.a02.TestBeanFactory$Bean2@52aa2946  

2.2ApplicationContext 的实现(常见四个)

要点:

  • 常见的 ApplicationContext 容器实现
  • 内嵌容器、DispatcherServlet 的创建方法、作用

ClassPathXmlApplicationContext(了解)

// ⬇️较为经典的容器, 基于 classpath 下 xml 格式的配置文件来创建
private static void testClassPathXmlApplicationContext() {
  ClassPathXmlApplicationContext context =
    new ClassPathXmlApplicationContext("a02.xml");

  for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
  }

  System.out.println(context.getBean(Bean2.class).getBean1());
}

FileSystemXmlApplicationContext

// ⬇️基于磁盘路径下 xml 格式的配置文件来创建
private static void testFileSystemXmlApplicationContext() {
    FileSystemXmlApplicationContext context =
            new FileSystemXmlApplicationContext(
                    "src\main\resources\a02.xml");
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}

AnnotationConfigApplicationContext

// ⬇️较为经典的容器, 基于 java 配置类来创建
private static void testAnnotationConfigApplicationContext() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(Config.class)
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}

AnnotationConfigServletWebServerApplicationContext

// ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
private static void testAnnotationConfigServletWebServerApplicationContext() {
  AnnotationConfigServletWebServerApplicationContext context =
    new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
  for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
  }
}

@Configuration
static class WebConfig {
  @Bean
  public ServletWebServerFactory servletWebServerFactory(){
    return new TomcatServletWebServerFactory();
  }
  @Bean
  public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet();
  }
  @Bean
  public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
    return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
  }
  @Bean("/hello")
  public Controller controller1() {
    return (request, response) -> {
      response.getWriter().print("hello");
      return null;
    };
  }
}