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 WebWeb 开发支持,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 实现类(如UserDaoMysqlImplUserDaoOracleImpl),必须修改 Service 层代码,违反开闭原则,维护成本极高。

二、解耦思路推导

  1. 第一步:工厂模式解耦创建工厂类,由工厂负责创建 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();
        }
    }
    

    问题:工厂类仍硬编码了实现类,切换实现类仍需修改工厂代码。

  2. 第二步:配置文件 + 反射解耦将实现类全类名写入配置文件,工厂通过反射创建对象:

    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();
        }
    }
    

    优势:切换实现类只需修改配置文件,无需修改代码,彻底解耦。

  3. 第三步:Spring IOC 的诞生Spring 将上述工厂模式、反射、配置文件的思想封装成IOC 容器,由容器统一管理所有对象的创建、依赖、生命周期,开发者只需配置,无需手动维护。


4、IOC 本质

一、IOC 核心定义

IOC(Inversion of Control,控制反转)是 Spring 的核心思想,将对象的创建权、依赖管理权从代码中反转给 Spring 容器,由容器负责对象的实例化、初始化、依赖注入和生命周期管理。

二、IOC 的本质

  • 控制反转:控制权从开发者(代码)反转给 Spring 容器
  • 依赖注入(DI):IOC 的具体实现方式,容器将依赖对象注入到目标对象中
  • 核心目标解耦,让代码更灵活、可维护、可扩展

三、IOC 容器的作用

  1. 对象创建与管理:统一创建、销毁所有 Bean,管理生命周期
  2. 依赖注入:自动为 Bean 注入依赖的其他 Bean
  3. 配置统一管理:通过 XML / 注解配置 Bean,无需硬编码
  4. 单例管理:默认单例模式,避免重复创建对象,提升性能

四、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 个实例不安全全局配置、缓存
websocketWebSocket 级每个 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"/>
  • 适用场景:线程不安全的对象、有状态的业务对象

三、作用域对比(面试高频)

特性singletonprototype
实例数量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)
@PostConstructBean 初始化后执行的方法(替代 init-method)
@PreDestroyBean 销毁前执行的方法(替代 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

二、高频面试题回顾

  1. Set 注入和构造器注入的区别?

    • Set 注入:通过setter方法,可选注入,灵活性高
    • 构造器注入:通过构造方法,强制注入,保证依赖完整性
  2. @Autowired 和 @Resource 的区别?

    • @Autowired:默认按类型装配,需@Qualifier指定名称
    • @Resource:默认按名称装配,失败则按类型,JSR-250 标准
  3. singleton 和 prototype 的区别?

    • singleton:单例,容器启动创建,全局共享,非线程安全
    • prototype:多例,每次获取创建,线程安全,容器不管理销毁
  4. 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.ProxyInvocationHandler

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 提供的MethodBeforeAdviceAfterReturningAdvice等接口,实现前置、后置通知,基于 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 是优秀的持久层框架,核心知识点如下:

  1. 核心特性:SQL 与代码分离、ORM 映射、动态 SQL、缓存(一级 / 二级)
  2. 核心组件SqlSessionFactorySqlSessionMapper接口、ResultMap
  3. 核心操作:CRUD、多表关联(association/collection)、动态 SQL(if/foreach
  4. 核心配置mybatis-config.xmlMapper.xml、别名、日志、分页

二、MyBatis 痛点

MyBatis 原生使用时,存在以下问题:

  • SqlSession手动创建、关闭,事务手动提交,代码冗余
  • Mapper接口需手动获取,无法与 Spring 容器整合
  • 事务管理复杂,无法与 Spring 声明式事务整合
  • 配置文件分散,无法统一管理

24、整合 MyBatis 方式一(XML 配置整合)

一、整合核心思路

Spring 整合 MyBatis 的核心是将 MyBatis 的SqlSessionFactoryMapper接口交给 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);
    }
}

三、整合核心优势

统一管理SqlSessionFactoryMapper、事务都交给 Spring 容器管理

简化操作:无需手动创建SqlSession,自动事务管理

声明式事务:可通过 Spring AOP 实现声明式事务,无需手动提交

配置统一:Spring 配置文件统一管理数据源、MyBatis 配置

25、整合 MyBatis 方式二(纯注解整合)

一、核心思路

方式二是纯 JavaConfig 注解式整合,完全替代 XML 配置,通过@Configuration+@MapperScan等注解,将 MyBatis 的SqlSessionFactoryMapper、数据源全部交给 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 动态代理实现:

  1. 为目标 Service 生成代理对象
  2. 方法执行前:通过事务管理器开启事务
  3. 方法执行:执行业务逻辑
  4. 方法执行后:无异常则提交事务,有异常则回滚事务

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(数据库,数据存储、事务执行)

四、高频面试题汇总(全栈核心)

  1. IOC 和 DI 的区别?
    • IOC 是控制反转,是设计思想;DI 是依赖注入,是 IOC 的具体实现。
  2. Spring AOP 的底层原理?
    • JDK 动态代理(基于接口)、CGLIB 动态代理(基于继承)。
  3. MyBatis 一级缓存和二级缓存的区别?
    • 一级缓存:SqlSession 级别,默认开启;二级缓存:Mapper 级别,需手动开启。
  4. Spring 事务的传播行为有哪些?
    • REQUIRED(默认)、SUPPORTS、MANDATORY、REQUIRES_NEW 等 7 种。
  5. MyBatis #{} 和 ${} 的区别?
    • #{} 是预编译,防 SQL 注入;${} 是字符串拼接,有注入风险。
  6. Spring 声明式事务的原理?
    • 基于 AOP 动态代理,在方法前后开启 / 提交 / 回滚事务。

五、学习建议与后续方向

  1. 夯实基础:吃透 MyBatis、Spring 的核心原理,不要只停留在会用
  2. 项目实战:做一个完整的 SSM 项目(如电商后台、管理系统),巩固知识
  3. 进阶学习:学习 SpringBoot、SpringCloud、MyBatis-Plus,跟上技术迭代
  4. 源码阅读:阅读 Spring、MyBatis 源码,理解底层实现,提升技术深度
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐