Spring

发布于 2026-07-30 19:32 更新于 2026-10-13 17:14 3125 字 16 min read ... 访问量 AI 审阅 文章内容已由 AI 审阅过,但仍需谨慎判断。 AI 改写 文章内容主要由他人撰写,AI 帮我改写或润色。

AI 审阅 文章内容已由 AI 审阅过,但仍需谨慎判断。

本文系统介绍了 Spring IoC 与依赖注入的核心概念、配置方式、Bean 生命周期、自动装配、循环依赖处理、注解使用、AOP 代理机制以及与 MyB及事务的整合。重点说明了依赖注入通过 Setter 或构造器实现,Bean 作用域与生命周期管理,以及通过注解和配置类实现组件自动注册和依赖注入,并结合 AOP 实现横切关注点如日志、事务等的解耦与管理。最后展示了 Spring 与 MyBatis 的集成实践,强调由 Spring 统一管理数据源、SqlSessionFactory 和事务,避免手动操作数据库会话。

Spring

IoC 与依赖注入

IoC 的核心思想

IoC 是控制反转。传统代码通常由对象自己创建依赖对象;使用 Spring 后,对象的创建、配置和依赖关系由 Spring IoC 容器统一管理。

依赖注入是 IoC 的一种具体实现方式。对象只声明自己需要哪些依赖,容器在创建 Bean 时把相应对象注入进去。

例如,Driver 依赖 Vehicle。业务类不再自行执行 new Car(),而是由容器创建 Car,再注入 Driver。

public interface Vehicle {
    void run();
}

public class Car implements Vehicle {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name + "正在行驶");
    }
}

public class Driver {
    private String name;
    private Vehicle vehicle;

    public void setName(String name) {
        this.name = name;
    }

    public void setVehicle(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    public void drive() {
        System.out.println(name + "准备出发");
        vehicle.run();
    }
}

Spring 项目搭建

创建 Maven 工程

在 pom.xml 中引入 spring-context。以下版本仅对应原课程示例环境:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>

创建核心配置文件

在资源目录中创建 applicationContext.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">
</beans>

启动容器

ApplicationContext context =
        new ClassPathXmlApplicationContext("applicationContext.xml");

Driver driver = context.getBean("driver", Driver.class);
driver.drive();

XML 配置 Bean

注册 Bean

<bean id="driver" class="com.hyxy.Driver"/>
<bean id="car" class="com.hyxy.Car"/>

id 是 Bean 在容器中的名称,class 是实现类的全限定类名。

Setter 注入

<bean id="car" class="com.hyxy.Car">
    <property name="name" value="小轿车"/>
</bean>

<bean id="driver" class="com.hyxy.Driver">
    <property name="name" value="老张"/>
    <property name="vehicle" ref="car"/>
</bean>

value 用于注入简单值,ref 用于引用容器中的另一个 Bean。

Setter 注入要求属性存在对应的 Setter 方法。Getter 方法不是完成注入的必要条件,但通常会按 JavaBean 规范一并提供。

构造器注入

public class Driver {
    private final String name;
    private final Vehicle vehicle;

    public Driver(String name, Vehicle vehicle) {
        this.name = name;
        this.vehicle = vehicle;
    }
}
<bean id="driver" class="com.hyxy.Driver">
    <constructor-arg index="0" value="老张"/>
    <constructor-arg index="1" ref="car"/>
</bean>

也可以通过 name 或 type 指定参数,但构造器重载较多时,使用 index 或明确的参数名更容易避免歧义。

构造器注入适合必需依赖,可以保证对象创建完成后处于可用状态。

Bean 作用域

Spring Bean 默认是单例作用域,即同一个容器中,同一 Bean 定义通常只创建一个实例。

<bean id="driver" class="com.hyxy.Driver" scope="singleton"/>

原型作用域会在每次获取 Bean 时创建新对象:

<bean id="driver" class="com.hyxy.Driver" scope="prototype"/>

常用作用域包括:

作用域含义
singleton每个 Spring 容器中通常只有一个实例。
prototype每次获取时创建一个新实例。
request每个 HTTP 请求一个实例。
session每个 HTTP Session 一个实例。
application每个 Web 应用一个实例。

Web 作用域需要在 Web 应用上下文中使用。

自动装配

XML 按名称装配

<bean id="driver"
      class="com.hyxy.Driver"
      autowire="byName">
    <property name="name" value="老张"/>
</bean>

<bean id="vehicle" class="com.hyxy.Car">
    <property name="name" value="小轿车"/>
</bean>

byName 会根据属性名查找同名 Bean。上例要求 Driver 的属性名为 vehicle。

XML 按类型装配

<bean id="driver"
      class="com.hyxy.Driver"
      autowire="byType">
    <property name="name" value="老张"/>
</bean>

<bean id="car" class="com.hyxy.Car"/>

byType 会根据属性类型查找 Bean。若同类型候选对象有多个,容器无法确定唯一依赖,通常会抛出异常。

XML 自动装配可用于学习原理,但实际项目更常使用构造器注入配合注解或 Java 配置。

循环依赖

循环依赖是指 Bean 之间形成相互依赖,例如 A 依赖 B,同时 B 又依赖 A。

Spring 可以解决部分单例 Bean 的 Setter 或字段注入循环依赖,但不能保证解决所有循环依赖。以下情况通常无法自动解决:

  • 构造器之间相互依赖。
  • prototype Bean 之间循环依赖。
  • 某些代理、初始化过程或自定义生命周期逻辑导致的循环依赖。

循环依赖通常反映类职责划分不合理,应优先通过重新设计依赖关系解决,而不是依赖容器兜底。

Bean 生命周期

XML 生命周期方法

<bean id="driver"
      class="com.hyxy.Driver"
      init-method="init"
      destroy-method="destroy"/>
public class Driver {
    public void init() {
        System.out.println("初始化 Driver");
    }

    public void destroy() {
        System.out.println("销毁 Driver");
    }
}

容器关闭时会调用单例 Bean 的销毁回调。Spring 不会自动完整管理原型 Bean 的销毁过程,原型对象使用完毕后通常由调用方负责清理资源。

注解生命周期方法

Spring 5 常见项目使用 javax.annotation,Spring 6 和 Spring Boot 3 使用 jakarta.annotation。

@PostConstruct
public void init() {
    System.out.println("初始化 Driver");
}

@PreDestroy
public void destroy() {
    System.out.println("销毁 Driver");
}

初始化流程概览

单例 Bean 的典型初始化流程可概括为:

  1. 实例化 Bean。

  2. 注入属性和依赖。

  3. 调用相关 Aware 接口。

  4. 执行 BeanPostProcessor 的前置处理。

  5. 执行初始化回调,例如 @PostConstruct、InitializingBean 或自定义 init-method。

  6. 执行 BeanPostProcessor 的后置处理,AOP 代理通常在此阶段创建。

  7. Bean 进入可用状态。

容器关闭时,会执行相应销毁回调。

BeanFactory 与 FactoryBean

BeanFactory

BeanFactory 是 Spring IoC 容器的基础接口,负责 Bean 的创建、获取和依赖管理。ApplicationContext 在其基础上增加了事件、国际化、资源加载等功能。

FactoryBean

FactoryBean<T> 本身是一个 Bean,但调用方通过普通 Bean 名称获取到的通常是它生产的对象。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
    private String configLocation;

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        try (InputStream inputStream =
                     Resources.getResourceAsStream(configLocation)) {
            return new SqlSessionFactoryBuilder().build(inputStream);
        }
    }

    @Override
    public Class<?> getObjectType() {
        return SqlSessionFactory.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
<bean id="sqlSessionFactory"
      class="com.hyxy.SqlSessionFactoryBean">
    <property name="configLocation" value="mybatis-config.xml"/>
</bean>

获取 sqlSessionFactory 时得到的是 FactoryBean 生产的对象;若要获取工厂 Bean 本身,需要在名称前加 &。

Bean 注册方式

Spring 常见的 Bean 注册方式包括 XML、组件扫描和 Java 配置。

XML 注册

<bean id="car" class="com.hyxy.Car"/>

XML 适合集中查看配置,也便于注册无法修改源码的第三方类,但配置量较大时会比较繁琐。

注解与组件扫描

先在配置文件中开启扫描:

<context:component-scan base-package="com.hyxy"/>

再用组件注解标记类:

@Component
public class Car {
}

常用分层注解包括:

  • @Controller:控制层组件。
  • @Service:业务层组件。
  • @Repository:数据访问层组件。
  • @Component:通用组件。

这些注解都能把类注册为 Spring Bean,分层名称主要用于表达职责;部分注解还可能参与框架的异常转换等机制。

依赖注入注解

@Autowired

@Autowired 主要按类型查找依赖。若存在多个同类型 Bean,可结合 @Qualifier 或 @Primary 消除歧义。

推荐使用构造器注入:

@Service
public class DriverService {
    private final Vehicle vehicle;

    public DriverService(@Qualifier("car") Vehicle vehicle) {
        this.vehicle = vehicle;
    }
}

只有一个构造器时,通常可以省略构造器上的 @Autowired。

@Qualifier

@Component("car")
public class Car implements Vehicle {
}

@Component("bus")
public class Bus implements Vehicle {
}
@Service
public class DriverService {
    private final Vehicle vehicle;

    public DriverService(@Qualifier("bus") Vehicle vehicle) {
        this.vehicle = vehicle;
    }
}

@Primary

@Component
@Primary
public class Car implements Vehicle {
}

同类型候选 Bean 有多个时,@Primary 标记的 Bean 会作为默认选择。

@Resource

@Resource 属于 Jakarta 或 Java 标准注解,通常先按名称匹配,再按类型匹配。它可以通过 name 显式指定 Bean 名称。

@Resource(name = "car")
private Vehicle vehicle;

@Value

@Value 可注入简单配置值或 Spring 表达式。

@Value("${car.name:默认车辆}")
private String carName;

复杂配置对象更适合使用 @ConfigurationProperties。

@Scope

@Component
@Scope("prototype")
public class Driver {
}

Java 配置

定义配置类

@Configuration
public class MyConfig {
    @Bean
    public Car car() {
        Car car = new Car();
        car.setName("小轿车");
        return car;
    }

    @Bean
    @Scope("prototype")
    public Driver driver(Car car) {
        Driver driver = new Driver();
        driver.setName("老张");
        driver.setVehicle(car);
        return driver;
    }
}

@Configuration 表明该类是配置类,@Bean 方法的返回对象会注册到容器中。方法参数会由容器自动注入。

通过方法参数表达依赖比在方法内部直接调用其他 @Bean 方法更清晰,也更容易测试。

启动 Java 配置容器

ApplicationContext context =
        new AnnotationConfigApplicationContext(MyConfig.class);

Driver driver = context.getBean(Driver.class);
driver.drive();

代理模式

代理模式包含目标对象、代理对象和双方共同遵循的抽象契约。代理对象在调用目标方法前后增加通用逻辑,例如日志、权限校验或事务管理。

静态代理

public interface HouseService {
    void rentHouse();
}

public class Owner implements HouseService {
    @Override
    public void rentHouse() {
        System.out.println("签合同并收款");
    }
}

public class Agent implements HouseService {
    private final HouseService target;

    public Agent(HouseService target) {
        this.target = target;
    }

    @Override
    public void rentHouse() {
        System.out.println("寻找房源并协商价格");
        target.rentHouse();
    }
}

静态代理需要为不同目标类型编写对应代理类,重复代码较多。

JDK 动态代理

JDK 动态代理基于接口创建代理对象。

HouseService target = new Owner();

HouseService proxy = (HouseService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        (proxyObject, method, args) -> {
            System.out.println("调用前处理");
            try {
                return method.invoke(target, args);
            } finally {
                System.out.println("调用后处理");
            }
        }
);

proxy.rentHouse();

CGLIB 动态代理

CGLIB 通过生成目标类的子类创建代理,不要求目标类实现接口。目标类不能是 final,需要代理的方法也不能是 final。

public class DaoMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(
            Object proxy,
            Method method,
            Object[] args,
            MethodProxy methodProxy) throws Throwable {
        System.out.println("开启事务");
        try {
            Object result = methodProxy.invokeSuper(proxy, args);
            System.out.println("提交事务");
            return result;
        } catch (Throwable e) {
            System.out.println("回滚事务");
            throw e;
        }
    }
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(EmpDao.class);
enhancer.setCallback(new DaoMethodInterceptor());

EmpDao empDao = (EmpDao) enhancer.create();
empDao.save();

不要使用已经过时的 Class.newInstance()。需要通过反射创建对象时,应使用 getDeclaredConstructor().newInstance()。

AOP

AOP 是面向切面编程,用于把日志、事务、权限等横切逻辑从业务代码中分离出来。

常用术语

  • 连接点:程序执行过程中可被增强的位置,例如方法调用。
  • 切入点:用于筛选需要增强的连接点。
  • 通知:在目标方法前后执行的增强逻辑。
  • 切面:切入点和通知的组合。
  • 目标对象:被增强的业务对象。
  • 代理对象:应用增强逻辑后的对象。

引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.20</version>
</dependency>

定义切面

@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(* com.hyxy.service..*(..))")
    public void serviceMethod() {
    }

    @Before("serviceMethod()")
    public void before() {
        System.out.println("执行方法前记录日志");
    }

    @AfterReturning(pointcut = "serviceMethod()", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("方法正常返回:" + result);
    }

    @AfterThrowing(pointcut = "serviceMethod()", throwing = "exception")
    public void afterThrowing(Throwable exception) {
        System.out.println("方法发生异常:" + exception.getMessage());
    }

    @After("serviceMethod()")
    public void after() {
        System.out.println("方法结束");
    }
}

开启注解 AOP:

<aop:aspectj-autoproxy/>

@After 类似 finally,无论方法正常返回还是抛出异常都会执行;只有正常返回时才执行 @AfterReturning。

Spring 事务概述

Spring 声明式事务基于 AOP。常用事务属性包括传播行为、隔离级别、超时、只读和回滚规则。

常见传播行为:

传播行为含义
REQUIRED有事务则加入,没有事务则创建。
REQUIRES_NEW总是创建新事务,并暂停当前事务。
SUPPORTS有事务则加入,没有事务则非事务运行。
NESTED在支持保存点的环境中使用嵌套事务范围。
@Service
public class UserService {
    @Transactional(rollbackFor = Exception.class)
    public void saveUser(User user) throws Exception {
        // 数据库操作
    }
}

默认情况下,Spring 对 RuntimeException 和 Error 回滚,对普通受检异常不自动回滚。需要对受检异常回滚时,应配置 rollbackFor。

Spring 与 MyBatis 整合

整合目标是由 Spring 管理数据源、SqlSessionFactory、Mapper 代理和事务。

Maven 依赖

以下版本为原课程示例组合:

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<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.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.20</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.33</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.23</version>
    </dependency>
</dependencies>

数据库配置

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
jdbc.maxActive=300
jdbc.initialSize=2
jdbc.maxWait=60000
jdbc.minIdle=1

Spring 配置

<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
       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
           http://www.springframework.org/schema/tx
           https://www.springframework.org/schema/tx/spring-tx.xsd
           http://mybatis.org/schema/mybatis-spring
           http://mybatis.org/schema/mybatis-spring.xsd">

    <context:property-placeholder location="classpath:db.properties"/>
    <context:component-scan base-package="com.hyxy"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
    </bean>

    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations"
                  value="classpath*:com/hyxy/mapper/*.xml"/>
        <property name="typeAliasesPackage" value="com.hyxy.po"/>
    </bean>

    <mybatis-spring:scan base-package="com.hyxy.mapper"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

SqlSessionFactoryBean 负责创建 MyBatis 的 SqlSessionFactory,Mapper 扫描器会为接口创建代理并注册到 Spring 容器。

Mapper 与 Service

public interface UserMapper {
    int insert(User user);
}
@Service
public class UserService {
    private final UserMapper userMapper;

    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    public void save(User user) throws Exception {
        userMapper.insert(user);
    }
}

业务层只需要注入 Mapper,不应自行创建 SqlSession。事务由 Spring 管理时,也不应在 Mapper 或 Service 中手动调用 commit()。

执笔 AI 改写

喜欢的话,留下你的评论吧~

加载中...