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 方式(Spring 实现):容器控制依赖,注入给对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// DataBaseDao:还是原来的类,不用改
public class DataBaseDao {
public List<Score> queryScores() { ... }
}

// ScoreService:只“声明依赖”,不“创建依赖”(控制权交给容器)
public class ScoreService {
// 声明需要DataBaseDao(通过属性+setter,告诉容器自己需要它)
private DataBaseDao dataBaseDao;

// setter方法:给容器提供“注入依赖”的入口
public void setDataBaseDao(DataBaseDao dataBaseDao) {
this.dataBaseDao = dataBaseDao;
}

public void calculateScore() {
List<Score> scores = dataBaseDao.queryScores(); // 直接用容器注入的依赖
}
}

// Spring配置(告诉容器要管理哪些bean,以及依赖关系)
<bean id="dataBaseDao" class="com.example.DataBaseDao"/>
<bean id="scoreService" class="com.example.ScoreService">
<!-- 容器创建scoreService时,把dataBaseDao注入进去(调用setter方法) -->
<property name="dataBaseDao" ref="dataBaseDao"/>
</bean>

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="..." class="...">
<!-- 这个bean的合作者和配置在这里 -->
</bean>

<bean id="..." class="...">
<!-- c这个bean的合作者和配置在这里 -->
</bean>

<!-- 更多bean 定义在这里 -->

</beans>
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- services -->

<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions for services go here -->

</beans>

下面的例子显示了数据访问对象(data access object) daos.xml 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>

<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions for data access objects go here -->

</beans>

从上面的例子可以看出,在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
3
4
5
// 示例:加载“服务层”和“数据层”的2个XML文件
ApplicationContext context = new ClassPathXmlApplicationContext(
"services.xml", // 服务层配置(如UserService、OrderService)
"daos.xml" // 数据层配置(如UserDao、OrderDao)
);

2、用<import/> 标签导入(更常用)

在一个 ’主XML配置文件‘ 中,通过<import resource="文件路径"/>标签导入其他XML,相当于把多个XML ‘合并’成一个

主配置文件(如applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
<beans>
<!-- 导入服务层配置:和主文件在同一目录/classpath位置 -->
<import resource="services.xml"/>
<!-- 导入资源配置:在主文件所在目录的“resources”子目录下 -->
<import resource="resources/messageSource.xml"/>
<!-- 前导斜线会被忽略,建议不写 -->
<import resource="/resources/themeSource.xml"/>

<!-- 主文件也可直接定义Bean -->
<bean id="systemConfig" class="com.example.SystemConfig"/>
</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
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 1. 头部声明context命名空间和对应的Schema -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" <!-- 声明context命名空间 -->
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context <!-- 引入context的Schema -->
https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 2. 用context命名空间的标签开启注解扫描 -->
<!-- base-package:指定要扫描的包(容器会递归扫描这个包下所有带注解的类) -->
<context:component-scan base-package="com.example.service, com.example.dao"/>

</beans>

来开启扫描功能。

使用容器

现在注册好了,又配置扫描好了,这样所有的配置已经准备就绪开始使用了

1
2
3
4
5
6
7
8
// 创建和配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// 检索配置的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用配置的实例
List<String> userList = service.getUsernameList();

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
2
3
4
5
6
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory - bean="serviceLocator"
factory - method="createClientServiceInstance"/>

4、确定Bean的运行时类型:由于Bean元数据定义中的类可能与实际运行时类型不同,且AOP代理可能会包装Bean实例,所以确定Bean运行时可以用BeanFactory.getType方法来获取Bean的实际运行时类型。

总结:静态方法可以不受IOC容器管控也能使用,然后方法三是利用IOC容器中已经实例化后的Bean的初始化方法来创建另一个Bean

总结

现在IOC容器已经被创建了,配置也配置好了,那么Bean是如何纳入容器管理,Bean又是如何初始化的呢?

  1. 容器启动(如ClassPathXmlApplicationContext初始化),触发refresh过程,加载并解析配置元数据;

  2. 将解析结果封装为 BeanDefinition,注册到BeanDefinitionRegistry(内部 Map);

  3. 容器根据 Bean 的作用域和懒加载设置,在合适的时机(启动时或getBean时)通过AbstractBeanFactory

    的实现类创建 Bean:

    • 先反射实例化原始对象;
    • 再进行属性填充(处理@Value@Autowired等依赖);
    • 执行初始化逻辑(InitializingBeaninit-method等);
  4. 单例 Bean 存入singletonObjects(单例池),原型 Bean 直接返回,最终通过getBean获取并使用

DI依赖注入

上面已经知道IOC容器如何创建实例Bean这里讲解如何注入属性。

两种DI的实现方式 构造器注入 和 Setter 注入

1、构造器注入

这种注入方式注入依赖后不可修改,一般为final修饰的,一般在需要强制注入的场景。

配置方式:无歧义的时候,直接按参数顺序配置

有歧义的时候,通过type 参数类型、index参数索引,从0开始、name参数名消除歧义

1
2
3
4
5
<!-- 用index消歧:第一个参数为int,第二个为String -->
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
1
2
3
4
5
6
7
8
9
10
public class ExampleBean {
private final int years; // 构造器注入后不可修改
private final String ultimateAnswer;

// 依赖通过构造器传入,由容器注入
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}

2、基于Setter的依赖注入

这个是容器先通过无参构造器/无参静态工厂创建Bean,再调用Setter方法注入依赖,依赖后续可以修改

通过<property>标签制定属性名和依赖,支持直接引用其他Bean 利用ref属性,或者设置基本类型值 value属性

1
2
3
4
5
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
<!-- 注入依赖的MovieFinder Bean -->
<property name="movieFinder" ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="examples.MovieFinder"/>
1
2
3
4
5
6
7
8
public class SimpleMovieLister {
private MovieFinder movieFinder; // 可选依赖,可后续修改

// Setter方法供容器注入依赖
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
循环依赖流程

场景:Bean A构造器依赖 Bean B,Bean B构造器依赖 Bean A,形成循环。容器会检测到这种循环,抛出BeanCurrentlyInCreationException

依赖配置细节

1、xml中配置的value属性,Spring会自动将字符串转换为目标类型比如int 、 boolean

  • ```xml
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    
    </bean>
    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
2
3
4
5
6
7
8
9
10
<!-- 在 child-config.xml 中 -->
<bean id="userService" class="com.example.UserService">
<property name="dataSource" ref="dataSource"/>
<!-- ❌ 默认先在本容器找,找不到就报错 -->
</bean>

<bean id="userService" class="com.example.UserService">
<property name="dataSource" parent="dataSource"/>
<!-- ✅ 明确表示:去父容器里找这个 Bean -->
</bean>

⚠️ 注意:refparent 是不同的:

  • 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 后销毁)。
  • 2、懒加载 lazy-init
    • 默认情况下,单例 Bean 在容器启动时预初始化;设置lazy-init="true"后,首次调用getBean()时才初始化。
    • 容器级配置:<beans default-lazy-init="true">(所有 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"/>

image-20251026154320663

在实际开发中通常用注解@Resource来配置

9、方法注入

这个解决的是单例Bean依赖原型Bean,假设一个单例Bean里面依赖了一个task是个原型Bean,然后每次都运行打印task的id,但是由于单例Bean是单例的那么这里的task只会被注入一次,即使task是原型模式,也只能拿到第一个实例,那么这时候Spring会使用CGLIB动态生成子类,重写抽象方法。(spring里面已经集成了)

1
2
3
4
5
6
7
8
9
10
public abstract class ServiceA {

// 定义一个抽象方法,返回你要的原型 Bean
public abstract Task createTask();

public void doWork() {
Task task = createTask(); // 每次调用都返回新实例!
System.out.println("当前任务ID: " + task.id);
}
}
1
2
3
4
5
6
<!-- XML 配置 -->
<bean id="serviceA" class="com.example.ServiceA"/>
<bean id="task" class="com.example.Task" scope="prototype"/>

<!-- 关键:告诉 Spring,createTask() 方法要被动态实现 -->
<lookup-method name="createTask" bean="task"/>
1
2
3
4
5
6
7
8
9
10
11
@Component
public abstract class ServiceA {

@Lookup // 标记这个方法应该返回一个原型 Bean
public abstract Task createTask();

public void doWork() {
Task task = createTask(); // 每次都是新的!
System.out.println("当前任务ID: " + task.id");
}
}
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
2
3
4
5
6
public class Singleton {
// 类加载时直接初始化,饿汉式
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // 私有构造器阻止外部创建
public static Singleton getInstance() { return INSTANCE; }
}

2、prototype 是每次请求创建新势力,容器仅负责实例化和配置,不管理后续生命周期

这个就容器仅负责实例化,依赖注入和初始化,不管理后续生命周期

3、Web相关作用域(request / session / application)

Web相关的Scope的Bean需要感知当前的HTTP请求 / 会话,但Spring容器本身运行在Servlet容器中,而HTTP请求是由Servlet容器(如Tomcat)的线程处理的。

要使用这些需要现在Web环境中注册 RequestContextListener 或 RequestContextFilter,确保HTTP请求与线程绑定。

  • web.xml配置监听器
1
2
3
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
  • 或配置过滤器
1
2
3
4
5
6
7
8
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • 然后用注解配置

@RequestScope、@SessionScope、@ApplicationScope分别对应三种Web作用域:

1
2
3
@RequestScope
@Component
public class LoginAction { ... }