Spring

发布于 2026-07-30 19:32 更新于 2026-07-30 19:32 3125 字 16 min read ... 访问量

本文系统介绍了 Spring IoC 与依赖注入的核心思想、配置方式、Bean 生命周期、自动装配、循环依赖、代理模式及 AOP 机制,并结合实际项目展示了 Spring 与 MyBaties 的整合流程。重点说明了依赖注入的实现方式(Setter 和构造器注入)、Bean 作用域、自动装配策略、生命周期回调以及事务管理的原理和实践,强调了合理设计依赖关系以避免循环依赖,并推荐使用注解与组件扫描提升开发效率。

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>

也可以通过 nametype 指定参数,但构造器重载较多时,使用 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. 执行初始化回调,例如 @PostConstructInitializingBean 或自定义 init-method

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

  7. Bean 进入可用状态。

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

BeanFactoryFactoryBean

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 对 RuntimeExceptionError 回滚,对普通受检异常不自动回滚。需要对受检异常回滚时,应配置 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()

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

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka