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 或字段注入循环依赖,但不能保证解决所有循环依赖。以下情况通常无法自动解决:
- 构造器之间相互依赖。
prototypeBean 之间循环依赖。- 某些代理、初始化过程或自定义生命周期逻辑导致的循环依赖。
循环依赖通常反映类职责划分不合理,应优先通过重新设计依赖关系解决,而不是依赖容器兜底。
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 的典型初始化流程可概括为:
-
实例化 Bean。
-
注入属性和依赖。
-
调用相关
Aware接口。 -
执行
BeanPostProcessor的前置处理。 -
执行初始化回调,例如
@PostConstruct、InitializingBean或自定义init-method。 -
执行
BeanPostProcessor的后置处理,AOP 代理通常在此阶段创建。 -
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()。
喜欢的话,留下你的评论吧~