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初始化完成后,销毁前执行