Spring
Spring
核心技术
Spring框架的控制反转IoC 容器。
Spring面相切面编程 AOP 技术。
此外还有Spring和AspectJ
IOC容器
Spring IoC容器和Bean简介
原本是 “对象自己找依赖”(比如 A 类要用到 B 类,A 自己 new B、自己找 B 的实例),现在是 “容器给对象送依赖”(A 不用管 B 怎么来,容器提前准备好 B,在创建 A 时主动 “塞” 给 A)—— 这种 “找依赖的权力从对象手里转到容器手里” 的反转,就是 IoC;而容器 “塞依赖” 的具体动作,就是 DI(依赖注入)
IoC 是 “设计原则”(核心思想是 “反转依赖控制权”),DI 是 “实现方式”(具体怎么把依赖给对象)—— 二者本质是同一概念的不同角度描述,Spring 用 DI 的方式实现了 IoC 原则。
一个对象(比如 A 类)要和其他对象(比如 B 类、C 类,也就是 A 的 “依赖”)合作,不用自己去创建或查找这些依赖,只需要 “明确告诉容器自己需要什么”—— 告诉的方式有 3 种:
- 构造参数:A 的构造方法里写
public A(B b) { ... }(告诉容器 “我需要 B”); - 工厂方法参数:如果 A 是通过工厂方法创建的,工厂方法里写
public static A createA(B b) { ... }(告诉容器 “创建我需要 B”); - 属性设置:A 里写
private B b;+ setter 方法public void setB(B b) { ... }(告诉容器 “我需要 B,创建后给我设进来”)。
Spring 的核心是 “IoC 容器”(可以理解为 “对象管家”),容器会提前创建好所有需要的依赖对象(比如 B、C),当容器创建 A(Spring 里的对象叫 “bean”)时,会按照 A 之前 “告诉” 的方式(构造参数 / 工厂参数 / 属性),把 B、C 主动 “塞” 到 A 里 —— 这个 “塞” 的动作就是 “依赖注入(DI)”。
- 这是最关键的一句,解释 “控制反转” 的 “反转” 到底是什么:
- 「传统方式(没有 IoC)」:Bean 自己控制依赖 → A 要用到 B,A 自己用
B b = new B();(直接构建),或者自己找个 “服务定位器” 查 B 的实例(比如B b = ServiceLocator.getB();)—— 控制权在 A 手里。 - 「IoC 方式」:容器控制依赖 → A 不用自己 new B、不用自己查 B,控制权转到了 IoC 容器手里 —— 这就是 “控制的反转”(从对象反转到容器)。
- 「传统方式(没有 IoC)」:Bean 自己控制依赖 → A 要用到 B,A 自己用
例子
IoC 方式(Spring 实现):容器控制依赖,注入给对象
1 | // DataBaseDao:还是原来的类,不用改 |
Spring IoC容器的两个核心组件
BeanFactory 和 ApplicationContext
Spring IoC 容器核心功能由两个接口支撑,他们的关系类似 基础班工具和升级版工具,ApplicationContext包含BeanFactory所有功能并且新增了更多特性。
1、BeanFactory接口
核心功能,能创建对象 Bean,组装对象之间的依赖关系,比如A 依赖B ,就会把 B 塞给 A ,不管对象是什么类型都能管。
2、ApplicationContext 接口
是BeanFactory 的 子接口,相当于继承了基础款的所有功能,是更强大的企业级 IoC容器。
新增了 AOP集成更方便:直接支持Spring AOP 不用额外复杂配置
国际化支持:能支持多语言消息 中文环境显示你好,英文环境显示Hello
事件发布:支持 事件通知 机制,比如某个对象状态变化时,自动通知其他关心这个变化的对象。
场景化扩展:针对特定场景提供专用容器,比如WebApplicationContext 专门给 Web 应用 比如Spring MVC 用,能更好地适配Web环境。
Bean的概念
Bean是Spring IoC容器管理的特殊对象,理解他的关系是区分 普通对象 和 Spring Bean
1、普通对象 vs Spring Bean
普通对象是自己new创建的对象,生命周期由自己控制,创建、销毁全靠代码 创建 -> 使用 -> 不可达 -> 销毁/回收。
Spring Bean:由Spring IoC容器,BeanFactory或者ApplicationContext负责实例化(创建对象),组装(处理依赖,比如给对象的属性赋值)、管理(控制对象的生命周期,比如什么时候创建,什么时候销毁)
2、Bean的 配置元数据
容器怎么知道要创建哪些 Bean、Bean 之间有什么依赖关系?靠 “配置元数据”—— 就是你告诉容器的 “清单”,比如:
- XML 配置文件(
<bean id="userService" class="com.xxx.UserService">...</bean>); - 注解(
@Component、@Service等,标记哪些类需要被容器管理); - Java 配置类(
@Configuration+@Bean注解,手动定义 Bean)。
这些元数据里会写明:要创建哪个类的对象、对象的依赖是谁、对象的初始化参数是什么等,容器照着 “清单” 干活。
IOC容器的概念
Spring IoC容器的工作机制
1、ApplicationContext 是 Spring IoC 容器的 实体代表,相当于一个智能工厂,核心职责有三个
- 实例化:按照规则创建应用程序需要的对象,比如Service、Dao等
- 配置:给对象设置属性,比如给UserService的name属性赋值
- 组装:处理对象之间的依赖 比如OrderService需要UserService,容器会自动把UserService 连接到 OrderService里
2、核心依赖,容器做这些事的依据是配置元数据,相当于给工程的生产清单,清单里写着:
- 要创建哪些类的对象,比如com.xxx.UserService
- 每个对象的属性怎么设置,比如UserService的timeout设置为3000
- 对象之间的依赖关系,比如OrderService 依赖 UserService
3、元数据格式:生产清单可以用3种形式写:
- xml 文件 传统方式:比如
<bean id="userService" class="com.xxx.UserService"/> - Java注解:比如在类上标@Service,告诉容器这个类要被管理
- Java代码:配置类的方式,比如用@Configuration + @Bean注解手动定义对象。
思路:
(1)定义xml文件,里面定义bean标签,因为后续每个bean标签会被解析为一个对象
(2)解析xml文件(dom4j),解析的时候,会解析xml文件中的bean标签,每个bean标签转换为一个bean对象,此对象包含两个属性:id、class,此对象用来存放bean的id和class值。
(3)因为xml文件中bean标签可能是多个,所以定义一个List集合,存储bean对象。
(4)遍历List集合,得到每一个bean对象,通过bean对象的class属性,反射创建对应的对象。
(5)对象创建好以后,将bean对象的id和反射创建的对象,放入map集合中。
(6)定义一个工厂,(2)-(5)步骤放在工厂的构造器中完成
(7)工厂中定义获取对象的方法,通过id从map集合中获取对象。
ApplicationContext的具体实现和使用场景
Spring提供了多个ApplicationContext的视线,就像工厂有 不同的生产线,适配不同的场景。
1、独立应用 非Web 常用的两种
- ClassPathXmlApplicationContext:从项目的 类路径 比如 src/main/resources目录 ,读取XML配置文件
- FileSystemXmlApplicationContext:从操作系统的文件系统路径 比如 D:/config/spring.xml 读取 xml 配置
2、Web应用:用专门的WebApplicationContext,适配Tomcat等Web 服务环境。
3、简化配置的技巧:
- 虽然XML是传统格式,但可以用少量XML配置 “开启注解支持” 比如
<context:component-scan>,之后主要使用@Service,@Autowired等注解管理Bean,不用写大量XML - 实际开发中几乎不用手动写代码创建容器:比如Web应用只需要在web.xml里加几行模版配置,容器会由 Web 服务器自动初始化
配置元数据
Spring IoC容器消费一种配置元数据。这种配置元数据代表了你,作为一个应用开发者,如何告诉Spring容器在你的应用中实例化、配置和组装对象。
基于XML的元数据并不是配置元数据的唯一允许形式。Spring IOC 容器本身与这种配置元数据的实际编写格式是完全解耦的。如今许多开发者用基于Java的配置
关于在Spring容器中使用其他形式的元数据的信息,请参见。
- 基于注解的配置 使用基于注解的配置元数据定义Bean。
- Java-based configuration 通过使用Java而不是XML文件来定义你的应用类外部的Bean。要使用这些特性,请参阅
@Configuration@Bean,@Import, 和@DependsOn注解。
Spring的配置包括至少一个,通常是一个以上的Bean定义,容器必须管理这些定义。基于XML的配置元数据将这些Bean配置为顶层 <beans/> 元素内的 <bean/> 元素。Java配置通常使用 @Configuration 类中的 @Bean 注解的方法。
这些Bean的定义对应于构成你的应用程序的实际对象。通常,你会定义服务层对象、持久层对象(如存储库或数据访问对象(DAO))、表现对象(如Web控制器)、基础设施对象(如JPA EntityManagerFactory)、JMS队列等等。通常,人们不会在容器中配置细粒度的domain对象,因为创建和加载domain对象通常是 repository 和业务逻辑的责任。
下面的例子显示了基于XML的配置元数据的基本结构。
1 |
|
id 属性是一个字符串,用于识别单个Bean定义。 |
|
|---|---|
class 属性定义了 Bean 的类型,并使用类的全路径名。 |
id 属性的值可以用来指代协作对象。本例中没有显示用于引用协作对象的XML。
实例化一个容器
配置好元数据了,那么就可以实例化一个容器然后就可以根据配置在容器中new对象了
在ApplicationContext 构造函数的一条或者多条路径是资源字符串,它让容器从各种外部资源(如本地文件系统、Java CLASSPATH)加载配置元数据
1 | ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml") |
然后接口层service的对象引用的持久层的dao层对象,然后dao层的对象引用具体的类,这样子的配置类有些许的不同,下面是例子
下面的例子显示了 service 对象(services.xml)配置文件。
1 |
|
下面的例子显示了数据访问对象(data access object) daos.xml 文件。
1 |
|
从上面的例子可以看出,在xml配置中,
service层的时候由于引用的是对象,所以就用 id 配置service的具体名称 和 class 来制定service的实现类路径,然后在标签下用property标签用name来重新自定义引用的dao的名称,用ref来确定 service用到的dao的名称
dao层的时候,直接用id来制定dao层的名称用class来制定dao层的类路径。
拆分、组织Spring的XML配置元数据
当项目的xml文件过多的时候,为了让复杂的项目配置更清晰,设置了几个规则,核心目的就是拆分,组织xml配置元数据,而不是把所有的Bean定义写在一个XML,臃肿难以维护。
两种整合多XML配置的方式
1、创建ApplicationContext 容器 时,直接传入所有XML文件的路径,容器会自动合并所有Bean定义
1 | // 示例:加载“服务层”和“数据层”的2个XML文件 |
2、用<import/> 标签导入(更常用)
在一个 ’主XML配置文件‘ 中,通过<import resource="文件路径"/>标签导入其他XML,相当于把多个XML ‘合并’成一个
主配置文件(如applicationContext.xml)
1 | <beans> |
容器只用加载主xml,就能自动加载所有导入的子XML。
XML导入的路径规则
首先classpath是Java运行时JVM用来查找类文件.clssh和资源文件xml等的路径集合
1、spring项目默认会给一个resources文件,在该文件夹下默认就是classpath路径,也就是说如果把xml放在里面就直接写文件名就好了不用配置文件路径。
2、如果需要配置就需要配置一下项目路径,然后不要写前导斜线
用`$ {..}这样的占位符语法,在运行时读取JVM系统属性,动态拼接路径.
那么这个占位符的值可以从多个来源获取,优先级如下
1、JVM系统属性在启动命令的时候 java -Dxxx xxx.jar这样启动的时候
2、操作系统变量
3、.yml配置文件
在spring中需要配置占位符解析,在springboot中自动启用,无需配置。
用context命名空间开启注解扫描,在xml里面配置
1 | <!-- 1. 头部声明context命名空间和对应的Schema --> |
来开启扫描功能。
使用容器
现在注册好了,又配置扫描好了,这样所有的配置已经准备就绪开始使用了
1 | // 创建和配置bean |
Bean概览
Bean包含的核心信息与容器的关系
容器的核心功能是管理Bean,容器内部通过BeanDefinition对象 存储Bean的定义信息,相当于Bean的数字档案,记录了创建和管理Bean的所有关键参数。
| 类别 | 核心属性 | 作用说明 |
|---|---|---|
| Bean 的 “身份标识” | Class、Name | - Class:Bean 的全路径类名(如com.example.UserService),容器反射创建实例- Name:Bean 的唯一标识符(如 XML 的id),用于容器内引用 |
| Bean 的 “行为规则” | Scope、Lazy initialization mode | - Scope:Bean 的作用域(如单例singleton、原型prototype),决定实例数量和生命周期- Lazy:是否懒加载(容器启动时不创建,首次使用时创建) |
| Bean 的 “依赖关系” | Constructor arguments、Properties、Autowiring mode | - 前两者:手动指定依赖(构造参数 / 属性注入)- Autowiring mode:自动装配规则(如按类型 / 名称注入依赖),减少手动配置 |
| Bean 的 “生命周期” | Initialization method、Destruction method | - 初始化方法:Bean 实例化后执行(如资源初始化)- 销毁方法:Bean 销毁前执行(如资源释放) |
1、Class 全路径类名,容器通过反射创建实例
2、Name 名称,Bean的唯一表示,用于容器内区分和引用
3、Scope 作用域 ,Bean的生命周期范围,比如单例singleton、原型prototype,决定容器创建Bean的实例数量和存活时间
4、构造参数/属性 ,一来注入的关键信息,指定Bean创建需要的参数或者属性值,以及依赖的其他Bean合作者
5、Autowiring mode自动装配模式 ,容器自动匹配并注入依赖的规则(如按类型、按名称),减少手动配置。
6、懒加载模式, 指定Bean 是否在容器启动时创建(默认立即创建),还是首次使用时才创建。
7、初始化/销毁方法, Bean生命周期回调方法,分别在Bean初始化完成后,销毁前执行
实例化Bean
Spring容器有三种方法实例化Bean
1、用构造函数实例化:容器通过反射调用构造函数直接创建 Bean,这类似于Java中的new 操作符。这个是根据bena中的id得到bean的名称还有bean中的class知道class类的位置,然后利用class.forname这个反射方法利用默认构造器在容器初始化的时候进行实例化。
2、用静态工厂方法进行实例化:容器调用类的静态工厂方法来创建Bean。这个时候需要在Bean标签里面用 factory -m 属性来制定工厂方法名称,并且这个方法必须是静态方法,因为实在初始化中开始创建的。
3、用实例工厂方法进行实例化:bean调用现有的Bean的非静态方法来创建新Bean。配置的时候,class属性留空,factory - bean 属性制定包含实例方法的Bean的名称,再用 factory - method 属性设置工厂方法名称
1 | <bean id="serviceLocator" class="examples.DefaultServiceLocator"> |
4、确定Bean的运行时类型:由于Bean元数据定义中的类可能与实际运行时类型不同,且AOP代理可能会包装Bean实例,所以确定Bean运行时可以用BeanFactory.getType方法来获取Bean的实际运行时类型。
总结:静态方法可以不受IOC容器管控也能使用,然后方法三是利用IOC容器中已经实例化后的Bean的初始化方法来创建另一个Bean
总结
现在IOC容器已经被创建了,配置也配置好了,那么Bean是如何纳入容器管理,Bean又是如何初始化的呢?
容器启动(如
ClassPathXmlApplicationContext初始化),触发refresh过程,加载并解析配置元数据;将解析结果封装为 BeanDefinition,注册到
BeanDefinitionRegistry(内部 Map);容器根据 Bean 的作用域和懒加载设置,在合适的时机(启动时或getBean时)通过AbstractBeanFactory
的实现类创建 Bean:
- 先反射实例化原始对象;
- 再进行属性填充(处理
@Value、@Autowired等依赖); - 执行初始化逻辑(
InitializingBean、init-method等);
单例 Bean 存入
singletonObjects(单例池),原型 Bean 直接返回,最终通过getBean获取并使用
DI依赖注入
上面已经知道IOC容器如何创建实例Bean这里讲解如何注入属性。
两种DI的实现方式 构造器注入 和 Setter 注入
1、构造器注入
这种注入方式注入依赖后不可修改,一般为final修饰的,一般在需要强制注入的场景。
配置方式:无歧义的时候,直接按参数顺序配置
有歧义的时候,通过type 参数类型、index参数索引,从0开始、name参数名消除歧义
1 | <!-- 用index消歧:第一个参数为int,第二个为String --> |
1 | public class ExampleBean { |
2、基于Setter的依赖注入
这个是容器先通过无参构造器/无参静态工厂创建Bean,再调用Setter方法注入依赖,依赖后续可以修改
通过<property>标签制定属性名和依赖,支持直接引用其他Bean 利用ref属性,或者设置基本类型值 value属性
1 | <bean id="simpleMovieLister" class="examples.SimpleMovieLister"> |
1 | public class SimpleMovieLister { |
循环依赖流程
场景:Bean A构造器依赖 Bean B,Bean B构造器依赖 Bean A,形成循环。容器会检测到这种循环,抛出BeanCurrentlyInCreationException
依赖配置细节
1、xml中配置的value属性,Spring会自动将字符串转换为目标类型比如int 、 boolean
- ```xml
</bean><property name="username" value="root"/> <property name="password" value="123456"/>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 简化方式:使用`p命名空间`(属性式配置),如`p:username="root"`,减少嵌套标签。
2、引用其他Bean 协作者
- 通过`<ref bean="beanId"/>`引用容器中的其他 Bean,确保依赖的 Bean 先初始化。(这个是容器做的不需要手动排序,容器会先扫描BeanDefinition然后会根据依赖进行初始化的排序)
- 父容器引用:使用`<ref parent="beanId"/>`引用父容器中的 Bean(适用于容器分层场景)。
- 简化方式:`p命名空间`中用`p:属性名-ref="beanId"`(如`p:user-ref="userBean"`)。
在Spring中,容器是可以分层的,你可以有多个ApplicationContext,他们之间形成父子关系
```java
// 父容器:通常放公共组件(数据源、事务管理器等)
ApplicationContext parent = new ClassPathXmlApplicationContext("parent-config.xml");
// 子容器:有自己的 Bean,也可以访问父容器的 Bean
ApplicationContext child = new ClassPathXmlApplicationContext("child-config.xml");
child.setParent(parent); // 设置父子关系
1 | <!-- 在 child-config.xml 中 --> |
⚠️ 注意:
ref和parent是不同的:
ref="xxx":先在当前容器找,找不到再去父容器找(默认行为)parent="xxx":强制只在父容器中查找,不在本地容器中找
3、内部Bean
在
<property>或<constructor-arg>内部直接定义 Bean(无需id),仅供当前 Bean 使用,无法被外部引用。示例:
1
2
3
4
5
6
7<bean id="outerBean" class="...">
<property name="innerBean">
<bean class="..."> <!-- 内部Bean -->
<property name="name" value="test"/>
</bean>
</property>
</bean>
4、集合类型(List、Set、Map、Properties)
分别使用
<list>、<set>、<map>、<props>标签配置,支持嵌套值或 Bean 引用。示例:
1
2
3
4
5
6<property name="someMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value-ref="bean2"/>
</map>
</property>集合合并:子 Bean 可通过
merge="true"继承父 Bean 的集合属性,并覆盖重复键值。
5、null与空字符串
- 空字符串:
<property name="email" value=""/> - null 值:
<property name="email"><null/></property>
6、构造函数参数的快捷配置
- 使用
c命名空间通过属性指定构造函数参数,如c:username="root"(按名称)或c:_0="root"(按索引,_避免 XML 语法冲突)。
7、依赖关系的特殊处理
- 1、通过depends-on 强制初始化顺序
- 当 Bean A 依赖 Bean B 的初始化(但无需注入 B),用
depends-on确保 B 先初始化。 - 示例:
<bean id="A" class="..." depends-on="B, C"/>(B 和 C 先于 A 初始化)。 - 单例 Bean 中,
depends-on还会控制销毁顺序(依赖的 Bean 后销毁)。
- 当 Bean A 依赖 Bean B 的初始化(但无需注入 B),用
- 2、懒加载 lazy-init
- 默认情况下,单例 Bean 在容器启动时预初始化;设置
lazy-init="true"后,首次调用getBean()时才初始化。 - 容器级配置:
<beans default-lazy-init="true">(所有 Bean 默认懒加载)。 - 注意:若懒加载 Bean 被非懒加载 Bean 依赖,仍会在容器启动时初始化。
- 默认情况下,单例 Bean 在容器启动时预初始化;设置
8、自动注入
Spring可以自动解析Bean之间的依赖关系,减少显示配置,拥有4种模式
| 模式 | 说明 |
|---|---|
no(默认) |
不自动注入,需显式通过<ref>配置。 |
byName |
按属性名匹配容器中的 Bean(如setUser(...)匹配名为user的 Bean)。 |
byType |
按属性类型匹配容器中的 Bean(若存在多个同类型 Bean,抛出异常)。 |
constructor |
类似byType,但用于构造函数参数(无匹配类型则抛异常)。 |
- 限制:无法自动注入基本类型、String 等;显式配置(如
<property>)优先级高于自动注入。 - 排除自动注入:通过
autowire-candidate="false"标记 Bean,使其不参与自动注入候选。 - 优先候选:通过
primary="true"标记 Bean,在多匹配时优先被选中。
1 | <bean id="computer" class="Computer" autowire="byType"/> |
在实际开发中通常用注解@Resource来配置
9、方法注入
这个解决的是单例Bean依赖原型Bean,假设一个单例Bean里面依赖了一个task是个原型Bean,然后每次都运行打印task的id,但是由于单例Bean是单例的那么这里的task只会被注入一次,即使task是原型模式,也只能拿到第一个实例,那么这时候Spring会使用CGLIB动态生成子类,重写抽象方法。(spring里面已经集成了)
1 | public abstract class ServiceA { |
1 | <!-- XML 配置 --> |
1 |
|
Bean的作用域
Bean定义相当于创建对象的配方,而 作用域 Scope 控制从改配方创建的对象实例的生命周期和可见范围。
作用域也是通过配置制定,无需在Java级别硬编码
Spring支持6种作用域
| 作用域 | 说明 | 适用场景 |
|---|---|---|
singleton |
(默认)每个 Spring IoC 容器中,Bean 定义对应唯一实例,所有请求共享此实例。 | 无状态 Bean(如工具类、DAO) |
prototype |
每次请求(注入或getBean())都会创建新实例。 |
有状态 Bean(如用户会话相关对象) |
request |
每个 HTTP 请求对应一个实例,请求结束后销毁。 | Web 应用,与单次请求相关的 Bean |
session |
每个 HTTP Session 对应一个实例,会话结束后销毁。 | Web 应用,与用户会话相关的 Bean |
application |
绑定到 ServletContext 生命周期,整个 Web 应用共享一个实例。 | Web 应用全局配置(类似 ServletContext) |
websocket |
绑定到 WebSocket 会话生命周期,适用于 STOMP 协议的 WebSocket 应用。 | 实时通信场景 |
1、singleton作用域,Spring单例是 每个容器每个Bean,而饿汉式单例是每个类加载器。
singleton是根据缓存机制实现的,因为每次初始化的时候,会把实例存入到一个map里面后续所有的请求直接从缓存中直接获取,不用重复创建。
饿汉式单例,是java设计模式,通过类加载时直接初始化静态实例,保证每个jvm终只有一个实例
1 | public class Singleton { |
2、prototype 是每次请求创建新势力,容器仅负责实例化和配置,不管理后续生命周期
这个就容器仅负责实例化,依赖注入和初始化,不管理后续生命周期
3、Web相关作用域(request / session / application)
Web相关的Scope的Bean需要感知当前的HTTP请求 / 会话,但Spring容器本身运行在Servlet容器中,而HTTP请求是由Servlet容器(如Tomcat)的线程处理的。
要使用这些需要现在Web环境中注册 RequestContextListener 或 RequestContextFilter,确保HTTP请求与线程绑定。
- web.xml配置监听器
1 | <listener> |
- 或配置过滤器
1 | <filter> |
- 然后用注解配置
@RequestScope、@SessionScope、@ApplicationScope分别对应三种Web作用域:
1 |
|





