理解Spring(一):Spring 与 IoC
什么是 Spring
Spring 是一个轻量级的企业级软件开发框架,它于2004年发布第一个版本,其目的是用于简化企业级应用程序的开发。
在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成,开发一个应用除了要开发业务逻辑之外,更多的是关注如何使这些对象协作来完成所需功能,而且要高内聚,低耦合。虽然一些设计模式可以帮我们达到这个目的,可是这又徒增了我们的负担。如果能通过配置的方式来创建对象,管理对象之间依赖关系,那么就能够减少许多工作,提高开发效率。Spring 框架主要就是来完成这个功能的。
Spring 框架除了帮我们管理对象及其依赖关系之外,还提供了面向切面编程的能力,在此基础上,允许我们对一些通用任务如日志记录、安全控制、异常处理等进行集中式管理,还能帮我们管理最头疼的数据库事务。此外,它还提供了粘合其他框架的能力,使我们可以方便地与各种第三方框架进行集成,而且不管是 Java SE 应用程序还是JavaEE应用程序都可以使用这个平台进行开发。
Spring 是基于 IoC 和 AOP 两大思想理论衍生而来的,可以说,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() 方法的主要执行流程:
- 调用
refreshBeanFactory()
方法,该方法会首先新建或重建 BeanFactory 对象,然后使用相应的BeanDefinitionReader
读取并解析 Bean 定义信息,将每个 Bean 定义信息依次封装成BeanDefinition
对象,并将这些BeanDefinition
对象注册到BeanDefinitionRegistery
。这一步,完成了 BeanFactory 的创建,以及 Bean 定义信息的加载。 - 配置 BeanFactory,对 BeanFactory 做一些后置处理,注册 Bean 的后置处理器,初始化消息源和事件多播器,注册监听器等。这一步,完成了 Spring 容器的配置工作。
- 调用
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的更多相关文章
- spring常用注解以IOC理解
使用注解来构造IoC容器 用注解来向Spring容器注册Bean.需要在applicationContext.xml中注册<context:component-scan base-package ...
- Spring(一):Spring入门程序和IoC初步理解
本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...
- [Spring框架]Spring IOC的原理及详解。
这里感谢 CSDN 的原博客:http://blog.csdn.net/m13666368773/article/details/7802126 看后 受益匪浅,这里再重温一遍Spring IOC ...
- spring源码浅析——IOC
=========================================== 原文链接: spring源码浅析--IOC 转载请注明出处! ======================= ...
- Spring MVC -- Spring框架入门(IoC和DI)
Spring MVC是Spring框架中用于Web应用开发的一个模块.Spring MVC的MVC是Model-View-Controller的缩写.它是一个广泛应用于图像化用户交互开发中的设计模式, ...
- 【转】Spring学习---Spring IoC容器的核心原理
[原文] Spring的两个核心概念:IoC和AOP的雏形,Spring的历史变迁和如今的生态帝国. IoC和DI的基本概念 IoC(控制反转,英文含义:Inverse of Control)是Spr ...
- spring源码解读-ioc
本系列博客结合我的理解,对spring的ioc进行简单分析,欢迎大家批评指正. beanfactory 我们从beanfactory开始,beanfactory是最根部的容器,描述了整个ioc的一些规 ...
- Spring升级案例之IOC介绍和依赖注入
Spring升级案例之IOC介绍和依赖注入 一.IOC的概念和作用 1.什么是IOC 控制反转(Inversion of Control, IoC)是一种设计思想,在Java中就是将设计好的对象交给容 ...
- Spring(三) Spring IOC
Spring 核心之 IOC 容器 再谈 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器 ...
- 【Spring】重新认识 IoC
前言 IoC (Inversion of control) 并不是Spring特有的概念. IoC 维基百科的解释: In software engineering, inversion of con ...
随机推荐
- 商城05——首页轮播图显示实现&Redis环境搭建&Redis实现缓存
1. 课程计划 1.首页轮播图的展示 2.首页大广告展示流程图 3.Redis的常用命令 4.Redis的服务器搭建 (集群的搭建) 5.向业务逻辑中添加缓存 6.Jedis的使用(redis的客 ...
- Spring系列.事务管理
Spring提供了一致的事务管理抽象.这个抽象是Spring最重要的抽象之一, 它有如下的优点: 为不同的事务API提供一致的编程模型,如JTA.JDBC.Hibernate和MyBatis数据库层 ...
- HTML5如何垂直居中一个浮动元素
html如何垂直居中一个浮动元素//方法一:已知元素的高宽#div1{background-color:#6699FF;width:200px;height:200px;position:absolu ...
- NFC芯片选型及基本电路框架
RFID作为一项专业度较高的技术,在一些公司,可能还会专门招聘专业的RFID工程师.本篇阐述的涉及到的只是基本选型设计.电路框架,关于RFID天线调试.低功耗检卡调试等,后续再其他篇章会继续更新! N ...
- 【题解】[USACO17JAN]Balanced Photo G
题目链接:https://www.luogu.com.cn/problem/P3608 方法一 用树状数组求逆序对先后扫两遍,一次从前往后,一次从后往前,算出每头奶牛左右两边比她高的数量. 最后统计一 ...
- Linux安装Redis 6.0.5 ./install_server.sh报错
Linux安装Redis 6.0.5 ./install_server.sh报错 linux 安装Redis6.0.5时 进行到./install_server.sh时报错, This systems ...
- node+ajax实战案例(5)
6.添加客户 6.1.点击添加按钮,弹出表单框 // 添加用户 显示对话框 var addBtn = document.getElementById('add-btn'); var addUser = ...
- GDI+ 双缓冲实现
早前曾为此问题在CSDN发帖求助(GDI+ 如何使用双缓冲绘制图像),得到了一个GDI+下较可行的方法,虽然绘制效果比直接绘制要好一些,不过还不能跟GDI的双缓冲方式比肩. 现在,我终于找到了一个 ...
- 【Xamarin.Forms 2】App基础知识与App启动
系列目录 1.[Xamarin.Forms 1]App的创建与运行 引言 本篇文章将介绍Xamarin.Forms中 App 基础知识和 App的启动. 开发环境 Visual Studio 2019 ...
- 五.数据库同步,创建django用户,用户登陆过程
(1)配置数据库 项目目录/settings.py是一个普通的python模块,每项配置都是一key/value 数据库的配置是以dict的形式存放在这个模块中,key名为:DATABASES DAT ...