Spring 入门核心笔记

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 实现类(如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>的核心属性:

    属性 作用
    id Bean 的唯一标识,用于获取 Bean
    class Bean 的全类名,用于反射创建对象
    name Bean 的别名,可多个(用逗号 / 空格分隔)
    scope Bean 的作用域(singleton/prototype 等)
    init-method Bean 初始化时执行的方法
    destroy-method Bean 销毁时执行的方法

二、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

二、高频面试题回顾

  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 源码,理解底层实现,提升技术深度
相关推荐
A__tao2 小时前
Elasticsearch Mapping 一键生成 Java 实体类(支持嵌套 + 自动过滤注释)
java·python·elasticsearch
KevinCyao2 小时前
java视频短信接口怎么调用?SpringBoot集成视频短信及回调处理Demo
java·spring boot·音视频
凯尔萨厮2 小时前
创建SpringWeb项目(Spring2.0)
spring·mvc·mybatis
迷藏4942 小时前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源
_李小白3 小时前
【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)
android·笔记·学习
wuxinyan1233 小时前
Java面试题47:一文深入了解Nginx
java·nginx·面试题
新知图书3 小时前
搭建Spring Boot开发环境
java·spring boot·后端
冰河团队3 小时前
一个拉胯的分库分表方案有多绝望?整个部门都在救火!
java·高并发·分布式数据库·分库分表·高性能
洛_尘3 小时前
Java EE进阶:Linux的基本使用
java·java-ee