什么是 Spring

Spring 是一个轻量级的企业级软件开发框架,它于2004年发布第一个版本,其目的是用于简化企业级应用程序的开发。

在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成,开发一个应用除了要开发业务逻辑之外,更多的是关注如何使这些对象协作来完成所需功能,而且要高内聚,低耦合。虽然一些设计模式可以帮我们达到这个目的,可是这又徒增了我们的负担。如果能通过配置的方式来创建对象,管理对象之间依赖关系,那么就能够减少许多工作,提高开发效率。Spring 框架主要就是来完成这个功能的。

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

Spring 是基于 IoCAOP 两大思想理论衍生而来的,可以说,Spring是一个同时实现了 IoC 和 AOP 的框架。

Spring 的整体架构

Spring 的整体架构如图所示:

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

什么是 IoC

IoC(Inverse Of Control,控制反转)是一种设计思想,目标是实现解耦。所谓控制反转,是指对资源的控制方式反转了。这里说的资源主要指我们的业务对象,对象之间往往会存在某种依赖关系,当一个对象依赖另一个对象时,传统的做法是在它内部直接 new 一个出来,即由对象自己负责创建并管理它所依赖的资源,这是传统的对资源的控制方式。IoC 就是将其颠倒过来,对象由主动控制所需资源变成了被动接受,由第三方(IoC 容器)对资源进行集中管理,对象需要什么资源就从IoC容器中取,或者让容器主动将所需资源注入进来。

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

IoC 的两种实现方式:

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

Bean 的概念

  • 在 Java 中,“Bean”是对用Java语言编写的“可重用组件”的惯用叫法。可以从字面意思去理解,Java 原本指爪哇咖啡,bean 指咖啡豆,而咖啡的“可重用组件”就是咖啡豆嘛。官方并没有说明所谓的“组件”具体指的是什么,因为“组件”本身就是一个抽象的概念,是对软件组成部分的抽象,因此,Bean作为可重用组件的代称,既可指类,也可指对象。
  • 在 Spring 中,Bean 的概念同上,有时也称 Component。 由 Spring 的 IoC容器所管理的 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 的定义如下:

可以断定,它就是 Spring 容器的核心,凡是作用域为单例的 Bean 的实例都保存在该 Map 集合中,我把它称之为单例池

那么 getBean 方法做了哪些事情呢?

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 对象,然后使用相应的BeanDefinitionReader 读取并解析 Bean 定义信息,将每个 Bean 定义信息依次封装成 BeanDefinition 对象,并将这些 BeanDefinition 对象注册到 BeanDefinitionRegistery。这一步,完成了 BeanFactory 的创建,以及 Bean 定义信息的加载。
  2. 配置 BeanFactory,对 BeanFactory 做一些后置处理,注册 Bean 的后置处理器,初始化消息源和事件多播器,注册监听器等。这一步,完成了 Spring 容器的配置工作。
  3. 调用 finishBeanFactoryInitialization() 方法,该方法会遍历之前注册到 BeanDefinitionRegistery 中的所有 BeanDefinition ,依次实例化那些非抽象非懒加载的单例 Bean,并将其加入单例池。这一步,完成了 Bean 的实例化。

Spring 容器的几个核心类:

  • DefaultListableBeanFactory 是一个通用的 BeanFactory 实现类,它还同时实现了BeanDefinitionRegistry 接口。从 ApplicationContext 实现类的源码中可以看到,在它内部维护着一个 DefaultListableBeanFactory 的实例,所有的 IoC 服务都委托给该 BeanFactory 实例来执行。
  • BeanDefinitionRegistry 负责维护 BeanDefinition 实例,该接口主要定义了registerBeanDefinition()getBeanDefinition() 等方法,用于注册和获取 Bean 信息。
  • BeanDefinition 用于封装 Bean 的信息,包括类名、是否是单例Bean、构造方法参数等信息。每一个 Spring Bean 都会有一个 BeanDefinition 的实例与之相对应。
  • BeanDefinitionReader 负责读取相应配置文件中的内容并将其映射到 BeanDefinition ,然后将映射后的 BeanDefinition 实例注册到 BeanDefinitionRegistry,由 BeanDefinitionRegistry 来保管它们。

Spring Bean 的注册与装配

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

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

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

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

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

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

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

  • 开启粗粒度的自动装配,即开启 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 的作用域与生命周期

未完待续

理解Spring(一):Spring 与 IoC的更多相关文章

  1. spring常用注解以IOC理解

    使用注解来构造IoC容器 用注解来向Spring容器注册Bean.需要在applicationContext.xml中注册<context:component-scan base-package ...

  2. Spring(一):Spring入门程序和IoC初步理解

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...

  3. [Spring框架]Spring IOC的原理及详解。

    这里感谢 CSDN 的原博客:http://blog.csdn.net/m13666368773/article/details/7802126 看后  受益匪浅,这里再重温一遍Spring IOC ...

  4. spring源码浅析——IOC

    =========================================== 原文链接: spring源码浅析--IOC   转载请注明出处! ======================= ...

  5. Spring MVC -- Spring框架入门(IoC和DI)

    Spring MVC是Spring框架中用于Web应用开发的一个模块.Spring MVC的MVC是Model-View-Controller的缩写.它是一个广泛应用于图像化用户交互开发中的设计模式, ...

  6. 【转】Spring学习---Spring IoC容器的核心原理

    [原文] Spring的两个核心概念:IoC和AOP的雏形,Spring的历史变迁和如今的生态帝国. IoC和DI的基本概念 IoC(控制反转,英文含义:Inverse of Control)是Spr ...

  7. spring源码解读-ioc

    本系列博客结合我的理解,对spring的ioc进行简单分析,欢迎大家批评指正. beanfactory 我们从beanfactory开始,beanfactory是最根部的容器,描述了整个ioc的一些规 ...

  8. Spring升级案例之IOC介绍和依赖注入

    Spring升级案例之IOC介绍和依赖注入 一.IOC的概念和作用 1.什么是IOC 控制反转(Inversion of Control, IoC)是一种设计思想,在Java中就是将设计好的对象交给容 ...

  9. Spring(三) Spring IOC

    Spring 核心之 IOC 容器 再谈 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器 ...

  10. 【Spring】重新认识 IoC

    前言 IoC (Inversion of control) 并不是Spring特有的概念. IoC 维基百科的解释: In software engineering, inversion of con ...

随机推荐

  1. Docker数据管理与挂载管理

    介绍如何在 Docker 内部以及容器之间管理数据:在容器中管理数据主要有两种方式:数据卷(Volumes).挂载主机目录 (Bind mounts) 镜像来源 [root@docker01 ~]# ...

  2. HTTP参数污染学习

    HTTP参数污染 --- HPP 参考: 参数污染漏洞(HPP)挖掘技巧及实战案例全汇总 视频内容 HPP,简而言之,就是给参数赋上多个值. 比如: https://www.baidu.com/s?w ...

  3. skywalking中表字段的信息

    https://skyapm.github.io/document-cn-translation-of-skywalking/zh/6.2.0/concepts-and-designs/scope-d ...

  4. python的常用魔法方法详细总结

    构造和初始化 __init__我们很熟悉了,它在对象初始化的时候调用,我们一般将它理解为"构造函数". 实际上, 当我们调用x = SomeClass()的时候调用,__init_ ...

  5. 二分查找法demo

    正文 中午闲着有点时间,做个demo睡觉去,这个例子网上应该都有,自己只是敲一下给自己做个记录. public static void main(String[] args) { int[] whit ...

  6. 梳理搭建SSM步骤

    以上全程手撕,如有不足或错误的,请指正!

  7. 学写PEP,参与Python语言的设计

    如果你为Python写了一篇PEP,这篇PEP成功的被Python指导委员会接受了,那么以后你在吹牛皮的时候你就可以说我主导了Python语言某个特性的设计工作. -- 跬蟒 我就问你主导Python ...

  8. 如何在linux下安装tomcat服务器

    linux作为现在比较主流的服务器操作系统,使用的机器广泛,安全稳定.tomcat作为应用容器当然可以有linux版本的tomcat.在linux上安装tomcat的方式也很简单,只需要运行脚本基本配 ...

  9. LeetCode 81,在不满足二分的数组内使用二分法 II

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第50篇文章,我们来聊聊LeetCode中的81题Search in Rotated Sorted ArrayII ...

  10. linux系统配置常用命令top

    本人测试系统:centos7 命令名称:top Linux top命令用于实时显示 process 的动态. 参数:-b 批处理 -c 显示完整的治命令 -I 忽略失效过程 -s 保密模式 -S 累积 ...