理解 Spring(一):Spring 与 IoC

什么是 Spring

Spring 是一个轻量级的企业级应用开发框架,于 2004 年诞生第一个版本,其初衷是简化企业级应用的开发。

在传统应用开发中,一个完整的应用由一组相互协作的对象组成,开发一个应用除了要开发业务逻辑之外,更多的是关注如何使这些对象协作来完成所需功能,而且要高内聚,低耦合。虽然一些设计模式可以达到这个目的,可是这又大大增加了开发成本。如果能够通过配置或声明的方式管理对象及其依赖关系,那么就可以减少很多不必要的工作,同时降低对象之间的耦合度。Spring 框架主要就是做这个事情的。

Spring 框架除了帮我们管理对象及其依赖关系之外,还提供了面向切面编程的能力,并在此基础上,允许我们对一些通用任务如日志记录、安全控制、异常处理等进行集中式管理,还能帮我们管理最头疼的数据库事务。此外,它还提供了粘合其他框架的能力,使我们可以方便地与各种第三方框架进行集成。

Spring 是基于 IoCAOP 两大思想理论衍生而来的,从技术角度来说,Spring 是一个同时实现了 IoC 和 AOP 的开发框架。

Spring 的整体架构

Spring 的整体架构如图所示:

img
img

Spring 核心模块有 3 个:Beans、Core、和 Context ,它们构建起了整个 Spring 的骨架,没有它们就不可能有 AOP、Web 等上层的功能特性。如果要在它们 3 个中选出一个最核心的模块的话,那就非 Beans 模块莫属了,Spring 创造了面向 Bean 的编程(BOP, Bean Oriented Programming),Bean 在 Spring 中才是真正的主角。关于 Bean 的概念,后面会进行介绍。

什么是 IoC

IoC(Inverse Of Control,控制反转)是一种设计思想,所谓控制反转,指的是对资源的控制方式进行反转。这里说的资源主要指我们的 Java 对象。在 OOP 中,对象之间往往存在某种依赖关系,当一个对象依赖另一个对象时,传统 OOP 的做法是在它内部直接 new 一个出来,这种做法是由对象自己主动创建并管理依赖资源。IoC 就是要将其颠倒过来,使得对象不再主动控制依赖资源,而是被动接受资源,IoC 要求将资源的控制权下放给第三方容器,这个第三方容器也称 Ioc 容器,它将对资源进行集中管理,对象需要什么资源就从容器中取,或者让容器主动将资源注入进来。

在 IoC 之后,对象与依赖资源之间不再具有强耦合性,资源可以被直接替换,而无需改动需求方的代码。举个不恰当的例子,董事长需要一个秘书,传统的做法是,董事长自己去指定一个秘书,控制权在他自己手上,但是这会导致他与秘书之间的耦合性较强,一旦他想换秘书了,就得修改自己的代码。IoC 的做法是,董事长声明自己的需求,然后等待 IoC 容器为他分配一个秘书,至于是哪个秘书,由容器说了算,如果需要换秘书,也是修改容器的配置文件,与董事长无关,这样就实现了两者间的解耦。

IoC 的两种实现方式:

  • DI(Dependency Injection,依赖注入)。所谓依赖注入,是指对象所依赖的资源将通过被动注入的方式得到,换言之,容器会主动地根据预先配置的依赖关系将资源注入进来。
  • DL(Dependency Lookup,依赖查找)。依赖查找是早先提出的一种 IoC 实现方式,它要求对象主动查找依赖资源,这种方式已经不再使用。

Bean 的概念

  • 在 Java 中,“Bean”是对“可重用组件”的惯用叫法(个人理解,Java 一词原本代指爪哇咖啡,bean 表示咖啡豆,那么咖啡豆不就是咖啡的“可重用组件”嘛)。组件本身就是一个抽象概念,Bean 作为其代称,也是一个抽象概念,当我们将一个类或一个对象作为组件来考虑时,就可以称它为 Bean。

  • 在 Spring 中,Bean 的概念同上,它有时也被称为 Component。由 Spring 容器管理的 Bean 则称为 Spring Bean。

  • 扩展:

    Java Bean 的概念不同于 Bean,Java Bean 是指符合 JavaBeans 规范的一类特殊的 Bean,即:所有属性均为 private,提供 getter 和 setter,提供默认构造方法。JavaBean 也可以认为是遵循特定约定的 POJO。

    POJO(Plain Ordinary Java Object)是指简单且普通的 Java 对象。严格来说,它不继承类,不实现接口,不处理业务逻辑,仅用于封装数据。

Spring 的基本使用

首先配置 Bean 信息,向 Spring 的 IoC 容器(或简称 Spring 容器)中注册 Bean。以 XML 方式为例,如下配置了两个 Bean,其中第一个依赖第二个:

<bean id="John" class="Person">
 <property name="lover">
  <ref bean="Mary"/>
 </property>
</bean>
<bean id="Mary" class="Person"/>

然后创建 Spring 容器,同时绑定配置文件。如下:

ApplicationContext container = new ClassPathXmlApplicationContext("bean-config.xml");

然后通过容器的 getBean 方法即可得到我们在配置文件中所配置的 Bean 的实例。如下:

Person John = container.getBean("John");

Spring 的两种 IoC 容器

Spring 提供了两种 IoC 容器: BeanFactory 和 ApplicationContext 。

  • BeanFactory 提供基本的 IoC 服务支持。
  • ApplicationContext 对 BeanFactory 进行了扩展与增强,除了拥有 BeanFactory 的所有能力外,还提供了许多高级特性,如事件发布、资源加载、国际化消息等。ApplicationContext 接口继承自 BeanFactory 接口,它的实现也是直接复用了 BeanFactory 的实现,因此可以说,ApplicationContext 是 BeanFactory 的增强版。

两者在核心功能上的区别主要是默认的加载策略不同,这点区别几乎可以忽略不计,通常情况下,我们总是使用更为强大的 ApplicationContext,很少会直接使用 BeanFactory。

以下是几个最常用的 ApplicationContext 实现类:

  • ClassPathXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigWebApplicationContext

Spring 容器的基本工作原理

既然是容器,那它最底层肯定是一种数据结构的实现。跟踪 getBean 方法,我们发现它是从一个叫作 singletonObjects 的 Map 集合中获取 Bean 实例的。singletonObjects 的定义如下:

img
img

这个 Map 集合保存了所有单例作用域的 Bean 实例,它是构成 Spring 容器的核心,我把它称之为单例池

getBean 方法首先会从单例池中获取 Bean 实例,如果取到了就直接返回,否则,如果有父容器,尝试从父容器中获取,如果也没获取到,则创建实例。创建实例之前会先确保该 Bean 所依赖的 Bean 全部初始化完成,接下来,如果是原型 Bean,创建好实例后将其直接返回,如果是单例 Bean,创建好实例后会将其先加入单例池,然后再从单例池中获取并返回。

下面我们来看一下 Spring 容器是如何完成初始化的。

ClassPathXmlApplicationContext为例,它的构造方法主要做的事情就是调用 refresh() 方法。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 准备好自己
        prepareRefresh();
        // 创建并初始化BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // 准备好要使用的BeanFactory
        prepareBeanFactory(beanFactory);
        try {
            // 对BeanFactory进行后置处理
            postProcessBeanFactory(beanFactory);
            // 调用BeanFactory的后置处理器
            invokeBeanFactoryPostProcessors(beanFactory);
            // 注册Bean的后置处理器
            registerBeanPostProcessors(beanFactory);
            // 初始化消息源
            initMessageSource();
            // 初始化事件多播器
            initApplicationEventMulticaster();
            // 初始化其他特殊的bean
            onRefresh();
            // 检测并注册监听器Bean
            registerListeners();
            // 实例化其余所有(非懒加载)的单例Bean
            finishBeanFactoryInitialization(beanFactory);
            // 最后一步:发布相应的事件
            finishRefresh();

refresh() 方法的主要执行流程:

  1. 调用 refreshBeanFactory() 方法,该方法会首先创建 BeanFactory 对象,然后读取并解析 Bean 定义信息,同时将这些 Bean 定义信息封装成一个个 BeanDefinition 对象,在 BeanDefinitionRegistery 中进行注册。这一步,主要完成了 BeanFactory 的创建,以及 Bean 定义信息的加载。
  2. 完成 Spring 容器的基本配置工作,包括:配置 BeanFactory,对 BeanFactory 做一些后置处理,注册 Bean 的后置处理器,初始化消息源和事件多播器,注册监听器等等。
  3. 调用 finishBeanFactoryInitialization() 方法,该方法会遍历所有的 BeanDefinition ,依次实例化每个非抽象非懒加载的单例 Bean,并将其加入单例池。这一步,主要完成了 Bean 的实例化。

Spring 容器的几个核心类:

  • DefaultListableBeanFactory 是一个通用的 BeanFactory 实现类,也是 ApplicationContext 的 IoC 服务提供者,此外,它还实现了BeanDefinitionRegistry 接口,同时扮演着 Bean 定义注册表的角色。
  • BeanDefinitionRegistry 表示 Bean 定义注册表,负责维护和管理 BeanDefinition 实例。
  • BeanDefinition 用于封装 Bean 定义信息,包括类名、构造方法参数、是否为单例 Bean等信息。

Spring Bean 的注册与装配

个人理解,注册与装配是不同的两个过程。注册指的是将 Bean 纳入 IoC 容器。装配指的是建立 Bean 之间的依赖关系。

Bean 的注册方式有以下三种:

  • 在 XML 文件中配置
  • 在 JavaConfig 中配置
  • 使用@ComponentScan@Component等注解进行配置

Bean 的装配分为手动装配和自动装配。

手动装配同样有三种方式:

  • 在 XML文件 中配置
  • 在 JavaConfig 中配置
  • 使用 @Resource 等注解来配置。
    • 这种方式既可以算作手动装配,也可以算作自动装配。当我们在 @Resource 注解中明确指定注入哪一个 Bean 时,我们称这是手动装配,而当我们不进行指定时,则算作自动装配。

自动装配也称自动注入,有两种开启方式:

  • 开启粗粒度的自动装配,即开启 Bean 的默认自动装配。即在``标签中配置default-autowire属性,或在@Bean注解中配置autowire属性。开启了默认自动装配的 Bean,Spring 会对它的全部属性都尝试注入依赖,这种方式不可控,因此很少使用。
  • 开启细粒度的自动装配,即在组件类中使用@Autowired等注解对单个属性开启自动装配。

Spring 支持以下四种用于自动装配的注解:

  • Spring 自带的 @Autowired 注解
  • JSR-330 的 @Inject 注解
  • JSR-250 的 @Resource 注解
  • Spring 新增的 @Value 注解,用于装配 String 和基本类型的值。@Value注解经常配合 SpEL 表达式一起使用。

SpEL 表达式的主要语法:

  • ${},表示从 Properties 文件中读取相应属性的值。通常需要同@PropertySource注解配合使用,该注解用于指定从哪个 Properties 文件中读取属性。
  • #{},表示从 Spring Bean 中读取相应属性的值。如,#{user1.name}表示从名称为 user1 的 Bean 中 读取 name 属性值。
  • 冒号:用于指定默认值,如${server.port:80}。

Spring Bean 的作用域与生命周期

Bean 的作用域主要有两种:

  • singleton:Bean 的默认作用域是 singleton,一个 singleton 的 Bean 被创建出来之后,它就会加入到单例池中缓存起来,然后在缓存中一直存在,由于 getBean 方法会先查缓存,只要缓存中有,就不会再创建了,因此,它在整个 Spring 容器的生命周期中只会有一个实例。
  • prototype:这种作用域的 Bean 不会被缓存起来,那么也就意味着,每次 getBean 时,查缓存都不命中,然后每次都会创建一个新的实例返回给我们。

Bean 的生命周期如下:

  1. 实例化;
  2. 填充属性(注入依赖);
  3. 回调 Aware 系列接口,包括 BeanNameAware、BeanFactoryAware 等等;
  4. 应用 Bean 的后置处理器,依次调用它们的 postProcessBeforeInitialization() 方法,这些后置处理器包括;
  5. 回调 InitializingBean 接口;
  6. 调用 init 方法,包括 @PostConstruct 注解所标注的方法以及 init-method 属性所指定的方法。
  7. 应用 Bean 的后置处理器,依次调用它们的 postProcessAfterInitialization() 方法;
  8. 如果作用域为单例,则加入到单例池中,之后便会一直存在,直到 Spring 容器被销毁。
  9. 当 Spring 容器被销毁时,回调 DisposableBean 接口,然后调用 destroy 方法,包括 @PreDestroy 注解所标注的方法以及 destroy-method 属性所指定的方法。
原文地址:https://www.cnblogs.com/yonghengzh/p/13311192.html