1、Spring 简介
一、Spring 核心定义
Spring 是由 Rod Johnson 创建的开源 Java 企业级开发框架 ,核心目标是简化 Java 开发,解决传统 EJB 开发的笨重、耦合度高的问题,是 Java 后端生态的基石。
二、Spring 核心特点
✅ 轻量级:非侵入式设计,核心 jar 包体积小,无额外依赖
✅ IOC(控制反转):将对象创建、依赖管理交给 Spring 容器,解耦代码
✅ AOP(面向切面编程):实现日志、事务、权限等横切逻辑的无侵入增强
✅ 容器化:管理对象的生命周期,提供统一的 Bean 管理
✅ 一站式:整合 MyBatis、SpringMVC、Redis 等所有主流 Java 技术栈
✅ 事务管理:提供声明式事务,简化数据库事务操作
三、Spring 发展历程
- 2003 年:Spring 1.0 发布,核心 IOC/AOP
- 2006 年:Spring 2.0 支持 XML 配置、AOP 增强
- 2013 年:Spring 4.0 支持 Java 8、注解开发
- 2017 年:Spring 5.0 支持响应式编程、SpringBoot
- 至今:Spring 生态已成为 Java 后端的事实标准
2、Spring 组成及拓展
一、Spring 核心组成(Spring Framework)
Spring 是一个全家桶式框架,核心模块如下:
| 模块 | 核心作用 |
|---|---|
| Spring Core | 核心容器,IOC/DI 的基础,Bean 管理 |
| Spring AOP | 面向切面编程,横切逻辑增强 |
| Spring Context | 上下文,提供 Bean 的访问、国际化、事件机制 |
| Spring DAO | 数据访问抽象,简化 JDBC 操作,事务管理 |
| Spring ORM | 整合 MyBatis、Hibernate 等 ORM 框架 |
| Spring Web | Web 开发支持,SpringMVC 的基础 |
| Spring Test | 单元测试、集成测试支持 |
二、Spring 生态拓展(Spring 全家桶)
Spring 不止是一个框架,更是一个完整的生态:
- SpringBoot:快速开发框架,自动配置,零 XML
- SpringCloud:微服务架构解决方案
- SpringSecurity:安全认证、权限控制
- SpringData:数据访问,支持 NoSQL、关系型数据库
- SpringBatch:批处理框架
- SpringAMQP:消息队列整合(RabbitMQ/RocketMQ)
3、IOC 理论推导
一、传统开发的痛点(耦合问题)
以用户业务为例,传统开发中 Service 层直接依赖 Dao 层实现类:
// 传统Dao层
public interface UserDao {
void addUser();
}
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
}
// 传统Service层:直接new Dao实现类,强耦合
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 硬编码,耦合度极高
public void addUser() {
userDao.addUser();
}
}
痛点 :如果需要切换 Dao 实现类(如UserDaoMysqlImpl、UserDaoOracleImpl),必须修改 Service 层代码,违反开闭原则,维护成本极高。
二、解耦思路推导
-
第一步:工厂模式解耦创建工厂类,由工厂负责创建 Dao 对象,Service 层从工厂获取对象:
public class DaoFactory { public static UserDao getUserDao() { return new UserDaoImpl(); } } public class UserService { private UserDao userDao = DaoFactory.getUserDao(); // 从工厂获取,解耦 public void addUser() { userDao.addUser(); } }问题:工厂类仍硬编码了实现类,切换实现类仍需修改工厂代码。
-
第二步:配置文件 + 反射解耦将实现类全类名写入配置文件,工厂通过反射创建对象:
properties
public class DaoFactory { private static Properties props; static { props = new Properties(); props.load(DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties")); } public static UserDao getUserDao() throws Exception { String className = props.getProperty("userDao"); // 反射创建对象,无需硬编码 return (UserDao) Class.forName(className).newInstance(); } }优势:切换实现类只需修改配置文件,无需修改代码,彻底解耦。
-
第三步:Spring IOC 的诞生 Spring 将上述工厂模式、反射、配置文件的思想封装成IOC 容器,由容器统一管理所有对象的创建、依赖、生命周期,开发者只需配置,无需手动维护。
4、IOC 本质
一、IOC 核心定义
IOC(Inversion of Control,控制反转)是 Spring 的核心思想,将对象的创建权、依赖管理权从代码中反转给 Spring 容器,由容器负责对象的实例化、初始化、依赖注入和生命周期管理。
二、IOC 的本质
- 控制反转:控制权从开发者(代码)反转给 Spring 容器
- 依赖注入(DI):IOC 的具体实现方式,容器将依赖对象注入到目标对象中
- 核心目标 :解耦,让代码更灵活、可维护、可扩展
三、IOC 容器的作用
- 对象创建与管理:统一创建、销毁所有 Bean,管理生命周期
- 依赖注入:自动为 Bean 注入依赖的其他 Bean
- 配置统一管理:通过 XML / 注解配置 Bean,无需硬编码
- 单例管理:默认单例模式,避免重复创建对象,提升性能
四、IOC 与 DI 的关系
- IOC:是一种设计思想,描述控制权的反转
- DI:是 IOC 的具体实现手段,通过依赖注入实现 IOC
- 两者本质是同一个概念的不同描述,IOC 是目标,DI 是实现方式
5、HelloSpring(第一个 Spring 程序)
一、环境准备
1. 导入 Maven 依赖
xml
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<!-- 日志依赖(可选,调试用) -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
2. 创建实体类
package com.example.pojo;
public class HelloSpring {
private String name;
public void sayHello() {
System.out.println("Hello, " + name + "!");
}
// getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3. 编写 Spring 配置文件(applicationContext.xml)
在src/main/resources下创建配置文件,注册 Bean:
xml
<?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 id="helloSpring" class="com.example.pojo.HelloSpring">
<!-- 注入属性:name为实体类属性,value为属性值 -->
<property name="name" value="Spring"/>
</bean>
</beans>
4. 测试类
import com.example.pojo.HelloSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloSpringTest {
public static void main(String[] args) {
// 1. 加载Spring配置文件,创建IOC容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从容器中获取Bean
HelloSpring helloSpring = context.getBean("helloSpring", HelloSpring.class);
// 3. 调用方法
helloSpring.sayHello(); // 输出:Hello, Spring!
}
}
二、运行结果
text
Hello, Spring!
✅ 第一个 Spring 程序运行成功!
6、IOC 创建对象方式
Spring IOC 容器创建对象的方式有多种,核心如下:
一、无参构造方法(默认方式,推荐)
Spring 默认通过无参构造方法创建对象,因此实体类必须提供无参构造(若未写构造,Java 默认提供;若写了有参构造,必须手动写无参构造)。
xml
<bean id="user" class="com.example.pojo.User">
<!-- 无参构造创建对象,通过setter注入属性 -->
<property name="name" value="张三"/>
</bean>
二、有参构造方法
通过constructor-arg标签指定构造参数,调用有参构造创建对象:
1. 实体类(带参构造)
public class User {
private String name;
private Integer age;
// 有参构造
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// getter/setter
}
2. XML 配置
xml
<bean id="user" class="com.example.pojo.User">
<!-- 方式1:按参数顺序 -->
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="18"/>
<!-- 方式2:按参数名(推荐,可读性高) -->
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="18"/>
<!-- 方式3:按参数类型(不推荐,参数类型相同时易冲突) -->
<constructor-arg type="java.lang.String" value="张三"/>
<constructor-arg type="java.lang.Integer" value="18"/>
</bean>
三、静态工厂方法创建
通过静态工厂方法创建对象,适合第三方类无法直接实例化的场景:
1. 静态工厂类
public class UserStaticFactory {
public static User getUser() {
return new User("张三", 18);
}
}
2. XML 配置
xml
<bean id="user" class="com.example.factory.UserStaticFactory" factory-method="getUser"/>
四、实例工厂方法创建
通过实例工厂的非静态方法创建对象:
1. 实例工厂类
public class UserInstanceFactory {
public User getUser() {
return new User("张三", 18);
}
}
2. XML 配置
xml
<!-- 先注册工厂实例 -->
<bean id="userFactory" class="com.example.factory.UserInstanceFactory"/>
<!-- 通过工厂实例的方法创建对象 -->
<bean id="user" factory-bean="userFactory" factory-method="getUser"/>
7、Spring 配置说明
一、核心配置文件结构
Spring 核心配置文件applicationContext.xml的根标签为<beans>,包含以下核心子标签:
-
<bean>:注册 Bean,定义对象的创建、属性、依赖 -
<import>:导入其他配置文件,分模块配置 -
<alias>:为 Bean 设置别名 -
<bean>的核心属性:属性 作用 idBean 的唯一标识,用于获取 Bean classBean 的全类名,用于反射创建对象 nameBean 的别名,可多个(用逗号 / 空格分隔) scopeBean 的作用域(singleton/prototype 等) init-methodBean 初始化时执行的方法 destroy-methodBean 销毁时执行的方法
二、Bean 的作用域(scope)
| 作用域 | 说明 | 适用场景 |
|---|---|---|
| singleton(默认) | 单例,整个容器中只有一个实例 | 工具类、Service、Dao 等无状态 Bean |
| prototype | 多例,每次获取 Bean 都创建新实例 | 有状态 Bean、线程不安全的对象 |
| request | 每次 HTTP 请求创建一个实例 | Web 应用,请求级数据 |
| session | 每个 HTTP Session 创建一个实例 | Web 应用,用户会话数据 |
| application | 整个 Web 应用共享一个实例 | 全局配置、缓存 |
三、配置拆分与导入
当项目变大时,可将配置按模块拆分,通过<import>导入:
xml
<!-- 主配置文件 applicationContext.xml -->
<beans>
<!-- 导入其他配置文件 -->
<import resource="spring-dao.xml"/>
<import resource="spring-service.xml"/>
<import resource="spring-mvc.xml"/>
</beans>
四、别名配置
通过<alias>为 Bean 设置别名,方便获取:
xml
<bean id="user" class="com.example.pojo.User"/>
<!-- 为user设置别名u -->
<alias name="user" alias="u"/>
获取 Bean 时,可通过context.getBean("u")获取。
8、DI 依赖注入环境
一、DI 核心定义
DI(Dependency Injection,依赖注入)是 IOC 的具体实现,Spring 容器在创建 Bean 时,自动将 Bean 依赖的其他 Bean / 属性注入到 Bean 中,无需手动 new 对象。
二、依赖注入的方式
1. Setter 注入(最常用,推荐)
通过setter方法注入属性,对应 XML 中的<property>标签:
xml
<bean id="user" class="com.example.pojo.User">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<!-- 注入Bean(依赖其他Bean) -->
<property name="userDao" ref="userDao"/>
</bean>
value:注入基本数据类型、字符串ref:注入其他 Bean(引用类型)
2. 构造器注入
通过有参构造注入属性,对应<constructor-arg>标签(见第 6 章),适合必须初始化的依赖。
3. 注入复杂类型(集合、数组、Map 等)
xml
<bean id="user" class="com.example.pojo.User">
<!-- 注入数组 -->
<property name="hobbies">
<array>
<value>篮球</value>
<value>音乐</value>
</array>
</property>
<!-- 注入List -->
<property name="books">
<list>
<value>Java核心技术</value>
<value>Spring实战</value>
</list>
</property>
<!-- 注入Map -->
<property name="cards">
<map>
<entry key="身份证" value="110101xxxx"/>
<entry key="银行卡" value="6222xxxx"/>
</map>
</property>
<!-- 注入Properties -->
<property name="info">
<props>
<prop key="name">张三</prop>
<prop key="age">18</prop>
</props>
</property>
</bean>
三、依赖注入的环境搭建
1. 依赖注入的前提
- 目标 Bean 和依赖 Bean 都必须在 Spring 容器中注册(
<bean>标签) - 目标 Bean 必须提供对应属性的
setter方法(Setter 注入)或有参构造(构造器注入)
2. 完整示例(Service 依赖 Dao)
// Dao层
public interface UserDao {
void addUser();
}
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
}
// Service层
public class UserService {
private UserDao userDao; // 依赖Dao
// Setter方法,用于注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
userDao.addUser();
}
}
xml
<!-- 注册Dao -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 注册Service,注入Dao依赖 -->
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
// 测试
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser(); // 输出:添加用户
四、依赖注入的优势
- 彻底解耦:Service 层不再依赖 Dao 实现类,切换实现类只需修改配置
- 可维护性高:依赖关系统一在配置文件中管理,一目了然
- 可测试性强:可通过注入 Mock 对象进行单元测试
- 灵活性高:无需修改代码,通过配置即可调整依赖关系
9、依赖注入之 Set 注入
一、Set 注入核心定义
Set 注入是 Spring 最常用的依赖注入方式,通过调用 Bean 的setter方法,为 Bean 的属性赋值 / 注入依赖,是 IOC 的核心实现方式之一。
二、注入类型详解
1. 注入基本数据类型 / 字符串
xml
<bean id="user" class="com.example.pojo.User">
<!-- 基本类型/字符串:value属性直接赋值 -->
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="email" value="zhangsan@example.com"/>
</bean>
要求:Bean 必须提供对应属性的
setter方法,否则注入失败。
2. 注入引用类型(Bean 依赖)
xml
<!-- 先注册依赖的Bean -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 注册Service,注入Dao依赖 -->
<bean id="userService" class="com.example.service.UserService">
<!-- 引用类型:ref属性关联其他Bean的id -->
<property name="userDao" ref="userDao"/>
</bean>
核心作用:彻底解耦 Service 与 Dao 的硬编码依赖,切换实现类仅需修改配置。
3. 注入复杂类型(集合、数组、Map 等)
xml
<bean id="user" class="com.example.pojo.User">
<!-- 1. 注入数组 -->
<property name="hobbies">
<array>
<value>篮球</value>
<value>音乐</value>
<value>编程</value>
</array>
</property>
<!-- 2. 注入List集合 -->
<property name="books">
<list>
<value>Java核心技术</value>
<value>Spring实战</value>
<value>MyBatis从入门到精通</value>
</list>
</property>
<!-- 3. 注入Set集合 -->
<property name="tags">
<set>
<value>后端开发</value>
<value>Java</value>
<value>Spring</value>
</set>
</property>
<!-- 4. 注入Map集合 -->
<property name="cards">
<map>
<entry key="身份证" value="110101xxxx"/>
<entry key="银行卡" value="6222xxxx"/>
<entry key="社保卡" value="123456xxxx"/>
</map>
</property>
<!-- 5. 注入Properties -->
<property name="info">
<props>
<prop key="name">张三</prop>
<prop key="age">18</prop>
<prop key="email">zhangsan@example.com</prop>
</props>
</property>
<!-- 6. 注入null/空字符串 -->
<property name="address">
<null/>
</property>
<property name="remark" value=""/>
</bean>
三、Set 注入核心优势
✅ 通用性强:支持所有类型的属性注入,是 Spring 最主流的注入方式
✅ 可读性高:配置清晰,便于维护
✅ 无侵入性 :仅需提供setter方法,不影响业务逻辑
✅ 灵活性高:可通过配置动态调整属性值,无需修改代码
10、c 命名和 p 命名空间注入
一、核心作用
c 命名空间和 p 命名空间是 Spring 提供的XML 简化注入方式 ,用于替代传统的<property>/<constructor-arg>标签,让配置更简洁。
二、p 命名空间(Set 注入简化)
1. 引入命名空间
在applicationContext.xml的根标签中添加 p 命名空间:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!-- 引入p命名空间 -->
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
2. 简化 Set 注入
xml
<!-- 传统Set注入 -->
<bean id="user" class="com.example.pojo.User">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="userDao" ref="userDao"/>
</bean>
<!-- p命名空间简化:p:属性名="值",p:属性名-ref="Bean id" -->
<bean id="user" class="com.example.pojo.User"
p:name="张三"
p:age="18"
p:userDao-ref="userDao"/>
语法规则:
- 基本类型 / 字符串:
p:属性名="值"- 引用类型:
p:属性名-ref="Bean id"
三、c 命名空间(构造器注入简化)
1. 引入命名空间
在根标签中添加 c 命名空间:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!-- 引入c命名空间 -->
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
2. 简化构造器注入
xml
<!-- 传统构造器注入 -->
<bean id="user" class="com.example.pojo.User">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
<!-- c命名空间简化:c:属性名="值",c:属性名-ref="Bean id" -->
<bean id="user" class="com.example.pojo.User"
c:name="张三"
c:age="18"
c:userDao-ref="userDao"/>
语法规则:
- 基本类型 / 字符串:
c:属性名="值"- 引用类型:
c:属性名-ref="Bean id"- 无参名时:
c:_0="值"(按参数索引)
四、命名空间注入优缺点
✅ 优点 :配置简洁,减少 XML 冗余,提升开发效率❌ 缺点:可读性略低于传统标签,复杂场景(集合注入)不支持,仅适用于简单注入
11、Bean 的作用域
一、核心作用域分类
Spring 通过scope属性定义 Bean 的作用域,控制 Bean 的实例化次数和生命周期,核心作用域如下:
| 作用域 | 说明 | 实例化次数 | 线程安全 | 适用场景 |
|---|---|---|---|---|
| singleton(默认) | 单例模式 | 整个容器仅 1 个实例 | 不安全(共享实例) | 无状态 Bean(Service、Dao、工具类) |
| prototype | 多例模式 | 每次获取 Bean 都创建新实例 | 安全(独立实例) | 有状态 Bean(线程不安全的对象) |
| request | 请求级 | 每次 HTTP 请求创建 1 个实例 | 安全 | Web 应用,请求级数据 |
| session | 会话级 | 每个 HTTP Session 创建 1 个实例 | 安全 | Web 应用,用户会话数据 |
| application | 应用级 | 整个 Web 应用仅 1 个实例 | 不安全 | 全局配置、缓存 |
| websocket | WebSocket 级 | 每个 WebSocket 连接创建 1 个实例 | 安全 | WebSocket 应用 |
二、核心作用域详解
1. singleton(单例,默认)
- 特点:容器启动时创建实例,全局唯一,所有请求共享同一个实例
- 配置 :
<bean id="user" class="com.example.pojo.User" scope="singleton"/> - 注意:单例 Bean 非线程安全,避免在 Bean 中定义可变成员变量
2. prototype(多例)
- 特点 :每次获取 Bean(
getBean())时创建新实例,容器不管理销毁 - 配置 :
<bean id="user" class="com.example.pojo.User" scope="prototype"/> - 适用场景:线程不安全的对象、有状态的业务对象
三、作用域对比(面试高频)
| 特性 | singleton | prototype |
|---|---|---|
| 实例数量 | 1 个 | 多个 |
| 创建时机 | 容器启动时 | 每次获取时 |
| 生命周期 | 容器管理(创建 - 销毁) | 容器仅创建,不管理销毁 |
| 线程安全 | 不安全 | 安全 |
| 性能 | 高(无重复创建) | 低(频繁创建) |
12、自动装配 Bean
一、自动装配核心定义
自动装配是 Spring 提供的依赖注入简化方式 ,无需手动配置<property>,容器自动根据规则为 Bean 注入依赖,减少 XML 配置。
二、自动装配方式(XML 配置)
在<bean>标签中通过autowire属性指定自动装配规则:
| 装配方式 | 说明 | 适用场景 |
|---|---|---|
| no(默认) | 不自动装配,需手动<property> |
明确依赖,避免误装配 |
| byName | 根据属性名自动装配,属性名与 Bean id 一致 | 属性名与 Bean id 一致的场景 |
| byType | 根据属性类型自动装配,类型匹配唯一 Bean | 类型唯一的场景 |
| constructor | 根据构造器参数类型自动装配 | 构造器注入的场景 |
三、代码示例
1. byName 自动装配
xml
<!-- 注册Dao -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 自动装配:byName,属性名userDao与Bean id一致,自动注入 -->
<bean id="userService" class="com.example.service.UserService" autowire="byName"/>
原理:Service 的
userDao属性名与 Dao 的id="userDao"一致,自动注入。
2. byType 自动装配
xml
<!-- 注册Dao(类型唯一) -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 自动装配:byType,根据UserDao类型匹配,自动注入 -->
<bean id="userService" class="com.example.service.UserService" autowire="byType"/>
注意:byType 要求容器中该类型的 Bean 唯一,若存在多个,抛出异常。
四、自动装配优缺点
✅ 优点 :减少 XML 配置,提升开发效率❌ 缺点:依赖关系不明确,可读性差,可能出现误装配,复杂场景不推荐
13、注解实现自动装配
一、核心注解
Spring 提供了一系列注解,替代 XML 自动装配,实现零 XML 依赖注入,核心注解如下:
| 注解 | 作用 | 对应 XML |
|---|---|---|
@Autowired |
根据类型自动装配(byType) | autowire="byType" |
@Qualifier |
配合@Autowired,根据名称装配(byName) |
- |
@Resource |
根据名称自动装配(默认 byName,失败则 byType) | autowire="byName" |
@Inject |
与@Autowired功能一致(JSR-330 标准) |
- |
二、代码示例
1. @Autowired(按类型装配)
@Service
public class UserService {
// 按UserDao类型自动装配,容器中该类型唯一即可
@Autowired
private UserDao userDao;
public void addUser() {
userDao.addUser();
}
}
注意:若容器中存在多个同类型 Bean,需配合
@Qualifier指定名称。
2. @Autowired + @Qualifier(按名称装配)
@Service
public class UserService {
@Autowired
@Qualifier("userDaoMysqlImpl") // 指定Bean id,按名称装配
private UserDao userDao;
}
3. @Resource(按名称 / 类型装配)
@Service
public class UserService {
// 默认按名称"userDao"装配,失败则按类型装配
@Resource(name = "userDao")
private UserDao userDao;
}
三、注解自动装配前提
- 开启注解扫描:在 XML 中配置
<context:component-scan>
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描,扫描com.example包下的所有类 -->
<context:component-scan base-package="com.example"/>
</beans>
14、Spring 注解开发
一、核心组件注解
Spring 提供了一系列组件注解,替代 XML 中的<bean>标签,用于标记类为 Spring 管理的 Bean:
| 注解 | 作用 | 适用场景 |
|---|---|---|
@Component |
通用组件注解,标记类为 Bean | 通用工具类、组件 |
@Controller |
控制层组件注解 | SpringMVC Controller |
@Service |
业务层组件注解 | Service 层 |
@Repository |
持久层组件注解 | Dao 层 |
@Configuration |
配置类注解,替代 XML 配置 | JavaConfig 配置类 |
本质:
@Controller/@Service/@Repository都是@Component的衍生注解,功能一致,仅语义不同。
二、其他核心注解
| 注解 | 作用 |
|---|---|
@Value |
注入基本类型 / 字符串属性 |
@Scope |
指定 Bean 的作用域(singleton/prototype) |
@PostConstruct |
Bean 初始化后执行的方法(替代 init-method) |
@PreDestroy |
Bean 销毁前执行的方法(替代 destroy-method) |
@Lazy |
延迟初始化,容器启动时不创建实例,首次获取时创建 |
三、完整注解开发示例
1. Dao
@Repository // 标记为Dao层Bean
public class UserDaoImpl implements UserDao {
@Value("${jdbc.username}") // 注入配置文件属性
private String username;
@Override
public void addUser() {
System.out.println("添加用户,用户名:" + username);
}
}
2. Service 层
@Service // 标记为Service层Bean
@Scope("singleton") // 指定单例作用域
public class UserService {
@Autowired // 自动装配Dao
private UserDao userDao;
@PostConstruct // 初始化后执行
public void init() {
System.out.println("UserService初始化");
}
@PreDestroy // 销毁前执行
public void destroy() {
System.out.println("UserService销毁");
}
public void addUser() {
userDao.addUser();
}
}
3. 测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
context.close();
}
}
四、注解开发优势
✅ 零 XML 配置:减少 XML 冗余,代码更简洁
✅ 开发效率高:直接在类上标记注解,无需维护 XML
✅ 可读性高:组件职责清晰,便于维护
✅ 符合现代开发:SpringBoot 主流开发方式
15、使用 JavaConfig 实现配置
一、JavaConfig 核心定义
JavaConfig 是 Spring 提供的纯 Java 配置方式 ,完全替代 XML 配置,通过@Configuration+@Bean注解实现 Bean 的注册与依赖注入,是 SpringBoot 的核心配置方式。
二、核心注解
| 注解 | 作用 |
|---|---|
@Configuration |
标记类为配置类,替代 XML 配置文件 |
@Bean |
标记方法为 Bean,方法返回值为 Bean 实例,方法名为 Bean id |
@ComponentScan |
开启组件扫描,替代<context:component-scan> |
@PropertySource |
加载外部 properties 配置文件 |
@Import |
导入其他配置类,实现配置拆分 |
三、完整 JavaConfig 示例
1. 实体类
@Component
public class User {
@Value("张三")
private String name;
@Value("18")
private Integer age;
// getter/setter
}
2. 配置类
@Configuration // 标记为配置类
@ComponentScan(basePackages = "com.example") // 开启组件扫描
@PropertySource("classpath:jdbc.properties") // 加载配置文件
public class SpringConfig {
// 注册Bean:方法名为Bean id,返回值为Bean实例
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
@Bean
public UserService userService(UserDao userDao) { // 自动注入参数中的Bean
UserService userService = new UserService();
userService.setUserDao(userDao);
return userService;
}
}
3. 测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
public static void main(String[] args) {
// 加载JavaConfig配置类,创建容器
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
}
四、JavaConfig 优势
✅ 完全替代 XML:纯 Java 代码配置,类型安全,编译期检查
✅ 适合现代开发:SpringBoot、微服务主流配置方式
✅ 配置拆分灵活 :通过@Import拆分配置,便于维护
✅ 无 XML 冗余:代码即配置,可读性高
16、上周内容回顾
一、核心知识点梳理
1. 依赖注入
- Set 注入 :最常用,通过
setter方法注入属性 / 依赖 - c/p 命名空间:XML 简化注入方式,减少标签冗余
- 自动装配 :XML 的
autowire、注解的@Autowired/@Resource
2. Bean 作用域
- singleton(默认):单例,全局唯一
- prototype:多例,每次获取创建新实例
- request/session/application:Web 应用专属作用域
3. 注解开发
- 组件注解 :
@Component/@Controller/@Service/@Repository - 依赖注入注解 :
@Autowired/@Qualifier/@Resource - 配置注解 :
@Configuration/@Bean/@ComponentScan
4. JavaConfig
- 纯 Java 配置,完全替代 XML,SpringBoot 核心配置方式
- 核心注解:
@Configuration+@Bean+@ComponentScan
二、高频面试题回顾
-
Set 注入和构造器注入的区别?
- Set 注入:通过
setter方法,可选注入,灵活性高 - 构造器注入:通过构造方法,强制注入,保证依赖完整性
- Set 注入:通过
-
@Autowired 和 @Resource 的区别?
@Autowired:默认按类型装配,需@Qualifier指定名称@Resource:默认按名称装配,失败则按类型,JSR-250 标准
-
singleton 和 prototype 的区别?
- singleton:单例,容器启动创建,全局共享,非线程安全
- prototype:多例,每次获取创建,线程安全,容器不管理销毁
-
JavaConfig 和 XML 配置的区别?
- XML:传统配置,解耦代码,适合复杂配置
- JavaConfig:纯 Java 配置,类型安全,编译期检查,SpringBoot 主流
17、静态代理模式
一、代理模式核心定义
代理模式是 23 种设计模式中的结构型模式 ,核心思想是通过代理对象控制对真实对象的访问,在不修改真实对象代码的前提下,增强其功能(如日志、事务、权限校验)。
二、静态代理核心结构
静态代理需要手动编写代理类 ,代理类与真实对象实现同一个接口,在代理类中调用真实对象的方法,并添加增强逻辑。
1. 核心角色
- 抽象接口(Subject):定义真实对象和代理对象的公共方法
- 真实对象(RealSubject):被代理的目标对象,实现核心业务逻辑
- 代理对象(Proxy):持有真实对象的引用,在调用真实方法前后添加增强逻辑
三、代码实现(以租房为例)
1. 抽象接口(租房)
// 抽象接口:租房
public interface Rent {
void rent();
}
2. 真实对象(房东)
// 真实对象:房东,实现租房接口
public class Landlord implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
3. 代理对象(中介)
// 代理对象:中介,实现租房接口,持有房东引用
public class Proxy implements Rent {
private Landlord landlord;
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rent() {
// 前置增强:看房、签合同、收中介费
System.out.println("中介带看房、签合同、收中介费");
// 调用真实对象的方法
landlord.rent();
// 后置增强:售后、维修
System.out.println("中介提供售后、维修服务");
}
}
4. 测试类
public class StaticProxyTest {
public static void main(String[] args) {
// 真实对象:房东
Landlord landlord = new Landlord();
// 代理对象:中介,持有房东引用
Proxy proxy = new Proxy(landlord);
// 调用代理方法,实现增强
proxy.rent();
}
}
四、静态代理优缺点
✅ 优点:
- 不修改真实对象代码,符合开闭原则
- 实现简单,逻辑清晰,适合固定场景
❌ 缺点:
- 类爆炸问题:每个真实对象都需要手动编写代理类,代码冗余
- 扩展性差:接口修改时,代理类、真实类都需要同步修改,维护成本高
18、静态代理再理解
一、静态代理本质
静态代理的本质是编译期确定代理关系,代理类在编译前就已编写完成,运行时无法动态生成。
- 核心逻辑:代理类包装真实类,在真实方法前后添加增强逻辑
- 适用场景:真实对象固定、增强逻辑固定的简单场景
二、静态代理与 AOP 的关系
静态代理是 AOP 的基础实现思想,但静态代理是手动实现,而 AOP 是通过框架自动生成代理类,实现无侵入增强。
- 静态代理:手动编写代理类,耦合度高,扩展性差
- AOP:框架自动生成代理类,解耦,扩展性强,是静态代理的升级方案
三、静态代理优化思路
为解决静态代理的类爆炸问题,可通过通用代理类 + 反射 优化,但本质仍属于静态代理,无法彻底解决扩展性问题,因此引出动态代理。
19、动态代理详解
一、动态代理核心定义
动态代理是代理模式的升级方案,在运行时通过反射动态生成代理类,无需手动编写代理类,解决了静态代理的类爆炸问题,是 Spring AOP 的底层核心。
二、JDK 动态代理核心实现
JDK 动态代理是 Java 原生提供的动态代理方案,基于接口 实现,核心类为java.lang.reflect.Proxy和InvocationHandler。
1. 核心角色
- InvocationHandler :代理类的调用处理器,定义增强逻辑,所有代理方法的调用都会转发到
invoke方法 - Proxy :动态生成代理类的工具类,通过
newProxyInstance方法生成代理对象
三、代码实现(以租房为例)
1. 抽象接口(租房)
public interface Rent {
void rent();
}
2. 真实对象(房东)
public class Landlord implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
3. 调用处理器(InvocationHandler)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 调用处理器:定义增强逻辑
public class ProxyInvocationHandler implements InvocationHandler {
// 真实对象
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
// 代理方法调用时,自动执行invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("中介带看房、签合同、收中介费");
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("中介提供售后、维修服务");
return result;
}
// 生成代理对象
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 真实对象实现的接口
this // 调用处理器
);
}
}
4. 测试类
public class DynamicProxyTest {
public static void main(String[] args) {
// 真实对象:房东
Landlord landlord = new Landlord();
// 调用处理器
ProxyInvocationHandler handler = new ProxyInvocationHandler(landlord);
// 生成代理对象
Rent proxy = (Rent) handler.getProxy();
// 调用代理方法
proxy.rent();
}
}
四、动态代理优缺点
✅ 优点:
- 无需手动编写代理类,解决了静态代理的类爆炸问题
- 通用性强,一个调用处理器可代理任意真实对象
- 扩展性强,接口修改时无需修改代理类
❌ 缺点:
- JDK 动态代理必须基于接口,无法代理没有接口的类
- 基于反射实现,性能略低于静态代理
五、CGLIB 动态代理(补充)
CGLIB 是第三方动态代理库,基于继承实现,可代理没有接口的类,通过生成真实类的子类作为代理类,重写父类方法实现增强。Spring AOP 会自动选择 JDK 或 CGLIB 动态代理。
20、AOP 实现方式一(Spring API 实现)
一、AOP 核心定义
AOP(Aspect Oriented Programming,面向切面编程)是 Spring 的核心特性之一,通过横切技术,在不修改业务代码的前提下,为业务方法添加增强逻辑(如日志、事务、权限),实现业务逻辑与横切逻辑的解耦。
二、AOP 核心术语
| 术语 | 说明 |
|---|---|
| Aspect(切面) | 封装横切逻辑的类,如日志切面、事务切面 |
| JoinPoint(连接点) | 业务中可被拦截的方法,如 Service 层的所有方法 |
| Pointcut(切入点) | 拦截连接点的规则,如拦截所有以add开头的方法 |
| Advice(通知 / 增强) | 切面在切入点执行的增强逻辑,分为前置、后置、环绕、异常、最终通知 |
| Target(目标对象) | 被代理的真实对象 |
| Proxy(代理对象) | AOP 生成的代理对象 |
| Weaving(织入) | 将切面逻辑应用到目标对象,生成代理对象的过程 |
三、Spring AOP 实现方式一:基于 Spring API
通过实现 Spring 提供的MethodBeforeAdvice、AfterReturningAdvice等接口,实现前置、后置通知,基于 XML 配置切入点。
1. 业务接口与实现类
// 业务接口
public interface UserService {
void addUser();
void deleteUser();
}
// 业务实现类(目标对象)
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
2. 前置通知(日志增强)
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
// 前置通知:方法执行前执行
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("【前置日志】执行方法:" + method.getName());
}
}
3. 后置通知
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
// 后置通知:方法执行后执行
public class LogAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("【后置日志】方法执行完成:" + method.getName());
}
}
4. XML 配置(AOP 织入)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1. 注册目标对象 -->
<bean id="userService" class="com.example.service.UserServiceImpl"/>
<!-- 2. 注册通知(切面) -->
<bean id="logBeforeAdvice" class="com.example.advice.LogBeforeAdvice"/>
<bean id="logAfterAdvice" class="com.example.advice.LogAfterAdvice"/>
<!-- 3. AOP配置:织入通知 -->
<aop:config>
<!-- 切入点:拦截UserService的所有方法 -->
<aop:pointcut id="userServicePointcut" expression="execution(* com.example.service.UserService.*(..))"/>
<!-- 织入前置通知 -->
<aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="userServicePointcut"/>
<!-- 织入后置通知 -->
<aop:advisor advice-ref="logAfterAdvice" pointcut-ref="userServicePointcut"/>
</aop:config>
</beans>
5. 测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
}
四、方式一优缺点
✅ 优点 :基于 Spring 原生 API,实现简单,适合入门❌ 缺点:耦合度高,通知类需实现 Spring 接口,XML 配置繁琐,不推荐生产使用
21、AOP 实现方式二(自定义切面实现)
一、核心思路
方式二通过自定义切面类,无需实现 Spring 接口,在切面类中定义增强逻辑,通过 XML 配置切入点与切面的关联,解耦通知与 Spring API。
二、代码实现
1. 自定义切面类
// 自定义切面类:无需实现Spring接口
public class MyAspect {
// 前置通知
public void before() {
System.out.println("【前置增强】方法执行前");
}
// 后置通知
public void after() {
System.out.println("【后置增强】方法执行后");
}
// 环绕通知(可控制方法执行)
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("【环绕前置】方法执行前");
// 执行目标方法
joinPoint.proceed();
System.out.println("【环绕后置】方法执行后");
}
// 异常通知(方法抛出异常时执行)
public void afterThrowing() {
System.out.println("【异常增强】方法抛出异常");
}
// 最终通知(无论方法是否成功,都执行)
public void afterFinally() {
System.out.println("【最终增强】方法执行完成");
}
}
2. XML 配置(AOP 织入)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1. 注册目标对象 -->
<bean id="userService" class="com.example.service.UserServiceImpl"/>
<!-- 2. 注册自定义切面 -->
<bean id="myAspect" class="com.example.aspect.MyAspect"/>
<!-- 3. AOP配置:织入切面 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="userServicePointcut" expression="execution(* com.example.service.UserService.*(..))"/>
<!-- 切面:关联切入点与通知 -->
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut-ref="userServicePointcut"/>
<aop:after method="after" pointcut-ref="userServicePointcut"/>
<aop:around method="around" pointcut-ref="userServicePointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="userServicePointcut"/>
<aop:after-returning method="afterFinally" pointcut-ref="userServicePointcut"/>
</aop:aspect>
</aop:config>
</beans>
三、切入点表达式详解
切入点表达式execution(* com.example.service.UserService.*(..))语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数类型) [异常])
*:通配符,匹配任意..:匹配任意参数、任意子包- 常用示例:
execution(* com.example.service.*.*(..)):匹配 service 包下所有类的所有方法execution(* add*(..)):匹配所有以 add 开头的方法execution(* com.example..*Service.*(..)):匹配 com.example 包下所有以 Service 结尾的类的所有方法
四、方式二优缺点
✅ 优点:解耦,无需实现 Spring 接口,切面类可自定义,灵活性高,推荐使用
❌ 缺点:XML 配置仍较繁琐
22、注解实现 AOP
一、核心注解
Spring 提供了一系列 AOP 注解,替代 XML 配置,实现零 XML AOP 开发,核心注解如下:
| 注解 | 作用 |
|---|---|
@Aspect |
标记类为切面类 |
@Before |
前置通知,方法执行前执行 |
@After |
最终通知,无论方法是否成功,都执行 |
@AfterReturning |
后置通知,方法成功执行后执行 |
@AfterThrowing |
异常通知,方法抛出异常时执行 |
@Around |
环绕通知,可控制方法执行 |
@Pointcut |
定义切入点表达式,复用切入点 |
@EnableAspectJAutoProxy |
开启 AOP 注解支持(配置类中使用) |
二、代码实现
1. 自定义切面类
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 标记为切面类,注册为Bean
@Aspect
@Component
public class MyAnnotationAspect {
// 定义切入点:复用表达式
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void pointcut() {}
// 前置通知
@Before("pointcut()")
public void before() {
System.out.println("【注解前置增强】方法执行前");
}
// 后置通知
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("【注解后置增强】方法执行成功");
}
// 最终通知
@After("pointcut()")
public void after() {
System.out.println("【注解最终增强】方法执行完成");
}
// 异常通知
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("【注解异常增强】方法抛出异常");
}
// 环绕通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("【注解环绕前置】方法执行前");
Object result = joinPoint.proceed();
System.out.println("【注解环绕后置】方法执行后");
return result;
}
}
2. 配置类(开启 AOP)
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy // 开启AOP注解支持
public class SpringConfig {
}
3. 测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationAopTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
}
三、注解 AOP 优缺点
✅ 优点:零 XML 配置,开发效率高,解耦,SpringBoot 主流开发方式
❌ 缺点:切面逻辑与代码耦合,复杂场景可读性略低于 XML
23、回顾 MyBatis
一、MyBatis 核心知识点回顾
MyBatis 是优秀的持久层框架,核心知识点如下:
- 核心特性:SQL 与代码分离、ORM 映射、动态 SQL、缓存(一级 / 二级)
- 核心组件 :
SqlSessionFactory、SqlSession、Mapper接口、ResultMap - 核心操作 :CRUD、多表关联(
association/collection)、动态 SQL(if/foreach) - 核心配置 :
mybatis-config.xml、Mapper.xml、别名、日志、分页
二、MyBatis 痛点
MyBatis 原生使用时,存在以下问题:
SqlSession手动创建、关闭,事务手动提交,代码冗余Mapper接口需手动获取,无法与 Spring 容器整合- 事务管理复杂,无法与 Spring 声明式事务整合
- 配置文件分散,无法统一管理
24、整合 MyBatis 方式一(XML 配置整合)
一、整合核心思路
Spring 整合 MyBatis 的核心是将 MyBatis 的SqlSessionFactory、Mapper接口交给 Spring 容器管理,由 Spring 统一管理事务、依赖注入,简化 MyBatis 操作。
二、整合步骤(方式一:XML 配置)
1. 导入 Maven 依赖
xml
<dependencies>
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<!-- Spring JDBC(整合MyBatis必备) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MyBatis-Spring整合包(核心) -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 数据源:Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
2. 数据库配置文件(db.properties)
properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=root
3. Spring 配置文件(applicationContext.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1. 加载数据库配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 2. 配置数据源(Druid) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 3. 配置SqlSessionFactory(MyBatis核心) -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 加载MyBatis核心配置文件(可省略,直接在这配置) -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 加载Mapper映射文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 配置别名 -->
<property name="typeAliasesPackage" value="com.example.pojo"/>
</bean>
<!-- 4. 配置Mapper扫描(自动注册Mapper接口) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描Mapper接口包 -->
<property name="basePackage" value="com.example.mapper"/>
<!-- 关联SqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 5. 注册Service(注入Mapper) -->
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userMapper" ref="userMapper"/>
</bean>
</beans>
4. MyBatis 核心配置文件(mybatis-config.xml,可简化)
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 开启驼峰命名 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
5. Mapper 接口与 XML
// Mapper接口
public interface UserMapper {
List<User> getUserList();
}
xml
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserList" resultType="User">
SELECT * FROM user
</select>
</mapper>
6. Service 层
public class UserServiceImpl implements UserService {
// 注入Mapper(Spring自动注入)
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> getUserList() {
return userMapper.getUserList();
}
}
7. 测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyBatisSpringTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
List<User> userList = userService.getUserList();
userList.forEach(System.out::println);
}
}
三、整合核心优势
✅ 统一管理 :SqlSessionFactory、Mapper、事务都交给 Spring 容器管理
✅ 简化操作 :无需手动创建SqlSession,自动事务管理
✅ 声明式事务:可通过 Spring AOP 实现声明式事务,无需手动提交
✅ 配置统一:Spring 配置文件统一管理数据源、MyBatis 配置
25、整合 MyBatis 方式二(纯注解整合)
一、核心思路
方式二是纯 JavaConfig 注解式整合 ,完全替代 XML 配置,通过@Configuration+@MapperScan等注解,将 MyBatis 的SqlSessionFactory、Mapper、数据源全部交给 Spring 容器管理,是 SpringBoot 的底层整合逻辑。
二、完整代码实现
1. 核心依赖(同方式一,无需额外依赖)
xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
2. 数据库配置文件(db.properties)
properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=root
3. Spring 配置类(核心整合)
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:db.properties") // 加载配置文件
@MapperScan("com.example.mapper") // 扫描Mapper接口,替代XML中的MapperScannerConfigurer
@EnableTransactionManagement // 开启事务管理(后续事务用)
public class SpringConfig {
// 注入配置文件属性
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// 1. 配置数据源(Druid)
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
// 2. 配置SqlSessionFactory(MyBatis核心)
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 注入数据源
factoryBean.setDataSource(dataSource);
// 配置别名
factoryBean.setTypeAliasesPackage("com.example.pojo");
// 加载Mapper映射文件
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 开启MyBatis配置(驼峰命名等)
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(org.apache.ibatis.logging.log4j.Log4jImpl.class);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
// 3. 配置事务管理器(Spring事务核心)
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4. Mapper 接口与实体类(同方式一)
// Mapper接口
@Mapper // 可选,@MapperScan已扫描,可省略
public interface UserMapper {
List<User> getUserList();
int addUser(User user);
}
// 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String userName;
private String password;
private String email;
}
5. Service 层(事务注解准备)
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> getUserList() {
return userMapper.getUserList();
}
@Override
public void addUser(User user) {
userMapper.addUser(user);
}
}
6. 测试类
public class MyBatisSpringAnnotationTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
List<User> userList = userService.getUserList();
userList.forEach(System.out::println);
}
}
三、方式二(注解)vs 方式一(XML)对比
| 维度 | 方式一(XML 配置) | 方式二(注解配置) |
|---|---|---|
| 配置方式 | XML 文件 | Java 配置类 |
| 耦合度 | 低(配置与代码分离) | 略高(代码即配置) |
| 开发效率 | 低(XML 繁琐) | 高(零 XML,快速开发) |
| 适用场景 | 传统 SSM 项目、复杂配置 | SpringBoot、微服务、现代项目 |
| 类型安全 | 编译期不检查 | 编译期检查,避免配置错误 |
26、事务回顾
一、事务核心定义
事务(Transaction)是数据库操作的最小执行单元,由一组 SQL 语句组成,要么全部执行成功,要么全部执行失败,保证数据的一致性和完整性。
二、事务四大特性(ACID,面试必考)
| 特性 | 全称 | 说明 |
|---|---|---|
| A(原子性) | Atomicity | 事务是不可分割的最小单元,所有操作要么全成功,要么全回滚 |
| C(一致性) | Consistency | 事务执行前后,数据库的完整性约束不被破坏(如转账前后总金额不变) |
| I(隔离性) | Isolation | 多个事务并发执行时,互不干扰,隔离级别决定了并发的安全性 |
| D(持久性) | Durability | 事务一旦提交,对数据的修改永久生效,即使数据库宕机也不会丢失 |
三、事务的隔离级别(解决并发问题)
数据库并发操作会产生 3 种问题,通过隔离级别解决:
| 并发问题 | 说明 |
|---|---|
| 脏读 | 一个事务读取了另一个事务未提交的数据 |
| 不可重复读 | 一个事务内多次读取同一数据,结果不一致(另一个事务修改并提交了数据) |
| 幻读 | 一个事务内多次查询,结果集行数不一致(另一个事务插入 / 删除了数据) |
| 隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| 读未提交(Read Uncommitted) | 最低级别,可读取未提交数据 | 是 | 是 | 是 |
| 读已提交(Read Committed) | 只能读取已提交数据(Oracle 默认) | 否 | 是 | 是 |
| 可重复读(Repeatable Read) | 同一事务内多次读取结果一致(MySQL 默认) | 否 | 否 | 是 |
| 串行化(Serializable) | 最高级别,事务串行执行 | 否 | 否 | 否 |
四、Spring 事务的本质
Spring 事务是基于 AOP 的声明式事务,通过动态代理在方法执行前后开启、提交 / 回滚事务,无需手动编写 JDBC 事务代码,简化事务管理。
27、Spring 声明式事务
一、核心定义
Spring 声明式事务是 Spring 提供的无侵入式事务管理方案,通过 AOP 将事务逻辑与业务逻辑分离,仅需通过注解 / XML 配置,即可为业务方法添加事务,无需修改业务代码。
二、核心注解(@Transactional)
@Transactional是 Spring 声明式事务的核心注解,可标注在类 或方法上:
- 标注在类上:类中所有方法都开启事务
- 标注在方法上:仅该方法开启事务
- 优先级:方法注解 > 类注解
三、@Transactional核心属性
| 属性 | 说明 | 常用值 |
|---|---|---|
propagation |
事务传播行为(多方法调用时的事务规则) | REQUIRED(默认,支持当前事务,无则新建) |
isolation |
事务隔离级别 | DEFAULT(默认,使用数据库默认隔离级别) |
timeout |
事务超时时间(秒),超时自动回滚 | -1(默认,永不超时) |
readOnly |
是否为只读事务(优化查询性能) | false(默认,可读写) |
rollbackFor |
指定哪些异常触发回滚(默认仅 RuntimeException 回滚) | Exception.class(所有异常回滚) |
noRollbackFor |
指定哪些异常不触发回滚 | - |
四、事务传播行为(面试高频)
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 支持当前事务,若当前无事务则新建一个事务 |
| SUPPORTS | 支持当前事务,若当前无事务则以非事务方式执行 |
| MANDATORY | 必须在当前事务中执行,无事务则抛出异常 |
| REQUIRES_NEW | 新建一个事务,挂起当前事务 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 |
| NEVER | 以非事务方式执行,若当前有事务则抛出异常 |
| NESTED | 在当前事务内嵌套执行,独立回滚 / 提交 |
五、完整代码实现(注解式声明式事务)
1. 配置类(已开启事务)
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:db.properties")
@MapperScan("com.example.mapper")
@EnableTransactionManagement // 开启事务管理(必须!)
public class SpringConfig {
// 数据源、SqlSessionFactory、事务管理器配置同25章
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2. Service 层(添加事务注解)
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
// 为方法添加事务:所有异常回滚,超时30秒
@Transactional(rollbackFor = Exception.class, timeout = 30)
@Override
public void addUser(User user) {
userMapper.addUser(user);
// 模拟异常:事务自动回滚
// int i = 1/0;
}
@Override
public List<User> getUserList() {
return userMapper.getUserList();
}
}
3. XML 式声明式事务(补充,传统方式)
xml
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" rollback-for="Exception" timeout="30"/>
<tx:method name="delete*" rollback-for="Exception"/>
<tx:method name="update*" rollback-for="Exception"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- AOP织入事务 -->
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.example.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
六、声明式事务原理
Spring 声明式事务基于AOP 动态代理实现:
- 为目标 Service 生成代理对象
- 方法执行前:通过事务管理器开启事务
- 方法执行:执行业务逻辑
- 方法执行后:无异常则提交事务,有异常则回滚事务
28、总结和回顾(SSM 全栈知识体系梳理)
一、MyBatis 核心知识体系
| 模块 | 核心知识点 |
|---|---|
| 基础篇 | 环境搭建、SqlSessionFactory、CRUD、日志、ResultMap |
| 进阶篇 | 动态 SQL(if/foreach/where)、多表关联(association/collection)、分页 |
| 性能篇 | 一级缓存、二级缓存、Ehcache 自定义缓存 |
| 整合篇 | Spring 整合 MyBatis(XML / 注解两种方式) |
二、Spring 核心知识体系
| 模块 | 核心知识点 |
|---|---|
| IOC/DI | 控制反转、依赖注入、Set 注入、c/p 命名空间、Bean 作用域、自动装配、注解开发、JavaConfig |
| AOP | 代理模式(静态 / 动态)、AOP 术语、两种实现方式、注解 AOP |
| 事务 | ACID、隔离级别、传播行为、声明式事务(注解 / XML) |
| 整合篇 | 整合 MyBatis、事务管理、SSM 架构 |
三、SSM 整体架构流转图
用户请求 → SpringMVC(Controller,接收请求、参数校验)
↓
Spring(Service,业务逻辑、事务管理、AOP增强)
↓
MyBatis(Mapper,数据库操作、ORM映射、动态SQL)
↓
MySQL(数据库,数据存储、事务执行)
四、高频面试题汇总(全栈核心)
- IOC 和 DI 的区别?
- IOC 是控制反转,是设计思想;DI 是依赖注入,是 IOC 的具体实现。
- Spring AOP 的底层原理?
- JDK 动态代理(基于接口)、CGLIB 动态代理(基于继承)。
- MyBatis 一级缓存和二级缓存的区别?
- 一级缓存:SqlSession 级别,默认开启;二级缓存:Mapper 级别,需手动开启。
- Spring 事务的传播行为有哪些?
- REQUIRED(默认)、SUPPORTS、MANDATORY、REQUIRES_NEW 等 7 种。
- MyBatis #{} 和 ${} 的区别?
- #{} 是预编译,防 SQL 注入;${} 是字符串拼接,有注入风险。
- Spring 声明式事务的原理?
- 基于 AOP 动态代理,在方法前后开启 / 提交 / 回滚事务。
五、学习建议与后续方向
- 夯实基础:吃透 MyBatis、Spring 的核心原理,不要只停留在会用
- 项目实战:做一个完整的 SSM 项目(如电商后台、管理系统),巩固知识
- 进阶学习:学习 SpringBoot、SpringCloud、MyBatis-Plus,跟上技术迭代
- 源码阅读:阅读 Spring、MyBatis 源码,理解底层实现,提升技术深度