Spring依赖注入详解
2025/9/17大约 8 分钟
Spring依赖注入详解
依赖注入(Dependency Injection,DI)是Spring框架实现IoC(控制反转)的核心机制。通过依赖注入,Spring容器负责创建对象并维护对象之间的关系。
学习目标
- 理解依赖注入的概念和原理
- 掌握set注入和构造注入的使用方法
- 学会注入各种数据类型(简单类型、对象类型、集合类型)
- 了解Spring依赖注入的最佳实践
依赖注入概述
什么是依赖注入
核心概念
- 依赖:对象和对象之间的关联关系
- 注入:通过某种方式让对象产生关系的数据传递行为
- 依赖注入:Spring容器负责创建对象并自动装配对象之间的依赖关系
Bean管理的两个方面
- Bean对象的创建:由Spring容器负责
- Bean对象属性的赋值:通过依赖注入完成
依赖注入的实现方式
Spring支持两种主要的依赖注入方式:
- set注入:基于setter方法的注入
- 构造注入:基于构造方法的注入
set注入详解
基本原理
实现原理
set注入通过反射机制调用属性对应的setter方法来给属性赋值。
// Spring内部实现原理(简化版)
// 1. 通过属性名推断setter方法名
// 2. 通过反射调用setter方法
Method setMethod = clazz.getMethod("setUserDao", UserDao.class);
setMethod.invoke(object, userDaoInstance);
环境准备
创建新的Maven模块:spring6-002-dependency-injection
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring6-002-dependency-injection</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.version>6.0.0</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
外部Bean注入
1. 创建DAO层
package com.example.dao;
import lombok.NoArgsConstructor;
/**
* 用户数据访问层
*/
@NoArgsConstructor
public class UserDao {
public void insert() {
System.out.println("正在保存用户数据...");
}
}
2. 创建Service层
package com.example.service;
import com.example.dao.UserDao;
import lombok.Setter;
/**
* 用户业务逻辑层
*/
public class UserService {
// 依赖的UserDao对象
private UserDao userDao;
/**
* set注入必须提供setter方法
* Spring通过反射调用此方法完成依赖注入
*/
@Setter
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
userDao.insert();
}
}
3. 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置UserDao Bean -->
<bean id="userDaoBean" class="com.example.dao.UserDao"/>
<!-- 配置UserService Bean,并注入UserDao依赖 -->
<bean id="userServiceBean" class="com.example.service.UserService">
<!--
property标签用于set注入
name: 属性名(对应setter方法)
ref: 要注入的Bean的id
-->
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
4. 测试代码
package com.example.test;
import com.example.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DITest {
@Test
public void testSetDI() {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService =
context.getBean("userServiceBean", UserService.class);
userService.save();
}
}
属性名与setter方法的关系
命名规则
Spring通过以下规则从setter方法推断属性名:
setUserDao()
→userDao
setUsername()
→username
setPassword()
→password
重要:property
标签的name
属性值必须与setter方法对应,而不是字段名。
内部Bean注入
除了外部Bean注入,还可以使用内部Bean的方式:
<bean id="userServiceBean" class="com.example.service.UserService">
<property name="userDao">
<!-- 内部Bean定义 -->
<bean class="com.example.dao.UserDao"/>
</property>
</bean>
注意事项
内部Bean无法被其他Bean引用,仅供当前Bean使用。实际开发中较少使用。
构造注入详解
基本原理
构造注入通过调用构造方法来完成依赖注入,在对象创建时就完成了依赖关系的建立。
示例代码
1. 创建OrderDao
package com.example.dao;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class OrderDao {
public void deleteById() {
System.out.println("正在删除订单...");
}
}
2. 创建OrderService
package com.example.service;
import com.example.dao.OrderDao;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class OrderService {
private final OrderDao orderDao;
/**
* 构造注入:通过构造方法注入依赖
*/
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void delete() {
orderDao.deleteById();
}
}
3. 配置文件
<!-- 单参数构造注入 -->
<bean id="orderDaoBean" class="com.example.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.example.service.OrderService">
<!--
constructor-arg: 构造方法参数
index: 参数索引(从0开始)
ref: 要注入的Bean的id
-->
<constructor-arg index="0" ref="orderDaoBean"/>
</bean>
多参数构造注入
public class OrderService {
private final OrderDao orderDao;
private final UserDao userDao;
public OrderService(OrderDao orderDao, UserDao userDao) {
this.orderDao = orderDao;
this.userDao = userDao;
}
}
配置方式一:使用索引
<bean id="orderServiceBean" class="com.example.service.OrderService">
<constructor-arg index="0" ref="orderDaoBean"/>
<constructor-arg index="1" ref="userDaoBean"/>
</bean>
配置方式二:使用参数名
<bean id="orderServiceBean" class="com.example.service.OrderService">
<constructor-arg name="orderDao" ref="orderDaoBean"/>
<constructor-arg name="userDao" ref="userDaoBean"/>
</bean>
配置方式三:类型自动推断
<bean id="orderServiceBean" class="com.example.service.OrderService">
<!-- Spring会根据参数类型自动匹配 -->
<constructor-arg ref="orderDaoBean"/>
<constructor-arg ref="userDaoBean"/>
</bean>
简单类型注入
什么是简单类型
Spring支持的简单类型
根据Spring源码BeanUtils.isSimpleValueType()
方法,简单类型包括:
- 基本数据类型:
byte
、short
、int
、long
、float
、double
、boolean
、char
- 包装类型:
Byte
、Short
、Integer
、Long
、Float
、Double
、Boolean
、Character
- 字符串类型:
String
及其他CharSequence
子类 - 数值类型:
Number
子类 - 日期类型:
Date
子类、Temporal
子类 - 其他类型:
Enum
、URI
、URL
、Locale
、Class
- 数组类型:以上类型对应的数组
基本示例
package com.example.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class User {
private String name;
private Integer age;
private Boolean active;
}
<bean id="userBean" class="com.example.bean.User">
<!-- 简单类型注入使用value属性,不是ref -->
<property name="name" value="张三"/>
<property name="age" value="25"/>
<property name="active" value="true"/>
</bean>
重要区别
- 对象类型注入:使用
ref
属性 - 简单类型注入:使用
value
属性
数据源配置示例
实际开发中经常需要配置数据源,这是简单类型注入的典型应用:
自定义数据源示例
package com.example.datasource;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
@Data
@NoArgsConstructor
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
@Override
public String toString() {
return String.format("MyDataSource{driver='%s', url='%s', username='%s', password='%s'}",
driver, url, username, password);
}
// 实现DataSource接口的其他方法...
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
// 省略其他方法实现...
}
<bean id="dataSource" class="com.example.datasource.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
特殊类型注入
package com.example.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;
@Data
@NoArgsConstructor
public class ComplexBean {
// 日期类型
private Date date;
private LocalDate localDate;
// 枚举类型
private Season season;
// 网络类型
private URI uri;
private URL url;
// 其他类型
private Locale locale;
private Class<?> clazz;
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
<bean id="complexBean" class="com.example.bean.ComplexBean">
<!-- 日期格式必须符合Date.toString()的格式 -->
<property name="date" value="Fri Sep 30 15:26:38 CST 2022"/>
<property name="localDate" value="EPOCH"/>
<!-- 枚举值 -->
<property name="season" value="WINTER"/>
<!-- URI和URL -->
<property name="uri" value="/save.do"/>
<property name="url" value="http://www.baidu.com"/>
<!-- 其他类型 -->
<property name="locale" value="CHINESE"/>
<property name="clazz" value="java.lang.String"/>
</bean>
注意事项
- Date类型:字符串格式必须符合
Date.toString()
方法的输出格式 - URL类型:Spring 6会验证URL的有效性,无效URL会报错
- 枚举类型:使用枚举常量名称
数组和集合注入
数组注入
简单类型数组
package com.example.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Arrays;
@Data
@NoArgsConstructor
public class Person {
private String[] favoriteFoods;
@Override
public String toString() {
return "Person{favoriteFoods=" + Arrays.toString(favoriteFoods) + "}";
}
}
<bean id="person" class="com.example.bean.Person">
<property name="favoriteFoods">
<array>
<value>鸡排</value>
<value>汉堡</value>
<value>鹅肝</value>
</array>
</property>
</bean>
对象类型数组
package com.example.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {
private String name;
}
package com.example.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Arrays;
@Data
@NoArgsConstructor
public class Order {
private Goods[] goods;
@Override
public String toString() {
return "Order{goods=" + Arrays.toString(goods) + "}";
}
}
<!-- 先定义商品Bean -->
<bean id="goods1" class="com.example.bean.Goods">
<property name="name" value="苹果"/>
</bean>
<bean id="goods2" class="com.example.bean.Goods">
<property name="name" value="香蕉"/>
</bean>
<!-- 配置订单Bean -->
<bean id="order" class="com.example.bean.Order">
<property name="goods">
<array>
<ref bean="goods1"/>
<ref bean="goods2"/>
</array>
</property>
</bean>
级联属性赋值
级联属性赋值允许直接为对象的嵌套属性赋值:
package com.example.bean;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Clazz {
private String name;
}
@Data
@NoArgsConstructor
public class Student {
private String name;
private Clazz clazz;
}
<bean id="clazzBean" class="com.example.bean.Clazz"/>
<bean id="student" class="com.example.bean.Student">
<property name="name" value="张三"/>
<!-- 注意:以下两行配置的顺序不能颠倒 -->
<property name="clazz" ref="clazzBean"/>
<!-- 级联属性赋值,要求clazz属性必须有getter方法 -->
<property name="clazz.name" value="高三一班"/>
</bean>
级联属性要求
- 配置顺序不能颠倒:先注入对象,再设置级联属性
- 被级联的属性必须提供getter方法
- 实际开发中较少使用,了解即可
最佳实践
选择注入方式的建议
推荐做法
- 必需依赖:使用构造注入,确保对象创建时依赖完整
- 可选依赖:使用setter注入,提供更好的灵活性
- 不可变对象:优先使用构造注入
- 循环依赖:使用setter注入解决
代码优化建议
使用Lombok注解:减少样板代码
@Data // 生成getter/setter @NoArgsConstructor // 生成无参构造 @AllArgsConstructor // 生成全参构造 public class UserService { private UserDao userDao; }
使用final关键字:构造注入的字段声明为final
public class UserService { private final UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } }
合理命名:Bean的id要有意义,便于理解和维护
小结
通过本章学习,你已经掌握了:
✅ 依赖注入的基本概念和原理
✅ set注入和构造注入的使用方法
✅ 外部Bean和内部Bean的配置方式
✅ 简单类型和对象类型的注入区别
✅ 数组和集合类型的注入配置
✅ 级联属性赋值的使用场景
✅ 依赖注入的最佳实践
下一步
接下来我们将学习Spring的Bean作用域和生命周期,了解Spring容器如何管理Bean对象的创建和销毁。