跳到主要内容
Spring IoC 与 Spring DI 详解 | 极客日志
Java java
Spring IoC 与 Spring DI 详解 Spring IoC(控制反转)与 DI(依赖注入)的核心概念。通过对比传统对象创建与容器管理,阐述降低耦合度的优势。讲解 Bean 存储注解(@Controller, @Service 等)及获取方式。分析属性、构造器、Setter 三种依赖注入方式的优缺点,并提供多 Bean 注入冲突的解决方案(@Primary, @Qualifier, @Resource)。
ArchDesign 发布于 2026/3/26 更新于 2026/5/28 33 浏览一、IoC 与 DI
名词解释:
Spring 是一个装了众多工具对象的 IoC 容器。
IoC 思想:对象交给 Spring 管理,就是 IoC 思想。
IoC:Inversion of Control,控制反转。
控制权反转,需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器(IoC 容器。Spring 是一个 IoC 容器,所以有时 Spring 也称为 Spring 容器),程序中只需要依赖注入 (Dependency Injection, DI) 就可以了。
1.1 IoC
实现下面的需求:
在传统的实现中,我们将每个模块当成一个类:
public class NewCarExample {
public static void main (String[] args) {
Car car = new Car ();
car.run();
}
static class Car {
private Framework framework;
public Car () {
framework = new Framework ();
System.out.println("Car init...." );
}
public void run () {
System.out.println("Car run..." );
}
}
static class Framework {
private Bottom bottom;
public Framework () {
bottom = new ();
System.out.println( );
}
}
{
Tire tire;
{
.tire = ();
System.out.println( );
}
}
{
size;
{
.size = ;
System.out.println( + size);
}
}
}
Bottom
"Framework init..."
static
class
Bottom
private
public
Bottom
()
this
new
Tire
"Bottom init..."
static
class
Tire
private
int
public
Tire
()
this
17
"轮胎尺寸:"
但是如上面的代码,如果我们要修改一个参数,会导致整个调用链都跟着修改。
我们为解决上面耦合度过高,可以采取:
把由自己创建的下级类,改为传递的方式(也就是注入的方式),
每次调整只需要调整对应那个类的代码即可。
这样无论底层类如何变化,整个调用链是不用做任何改变的。
public class IocCarExample {
public static void main (String[] args) {
Tire tire = new Tire (20 );
Bottom bottom = new Bottom (tire);
Framework framework = new Framework (bottom);
Car car = new Car (framework);
car.run();
}
static class Car {
private Framework framework;
public Car (Framework framework) {
this .framework = framework;
System.out.println("Car init...." );
}
public void run () {
System.out.println("Car run..." );
}
}
static class Framework {
private Bottom bottom;
public Framework (Bottom bottom) {
this .bottom = bottom;
System.out.println("Framework init..." );
}
}
static class Bottom {
private Tire tire;
public Bottom (Tire tire) {
this .tire = tire;
System.out.println("Bottom init..." );
}
}
static class Tire {
private int size;
public Tire (int size) {
this .size = size;
System.out.println("轮胎尺寸:" + size);
}
}
}
1.2 DI DI: Dependency Injection(依赖注入)
容器在运行期间,动态地为应用程序提供运行时所依赖的资源,称之为依赖注入。
二、IoC 与 DI 的使用
Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能:
• 存
• 取
Spring 容器管理的主要是对象,这些对象,我们称之为 "Bean"。我们把这些对象交由 Spring 管理,由 Spring 来负责对象的创建和销毁。我们程序只需要告诉 Spring,哪些需要存,以及如何从 Spring 中取出对象
Service 层及 Dao 层的实现类,交给 Spring 管理:使用注解:@Component
在 Controller 层 和 Service 层 注入运行时依赖的对象:使用注解 @Autowired
像把前面的图书管理系统的 BookController 重构。
BookController 类:
package com.example.project.controller;
import com.example.project.model.BookInfo;
import com.example.project.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/book")
@RestController
@Component
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList () {
return bookService.getList();
}
}
package com.example.project.service;
import com.example.project.dao.BookDao;
import com.example.project.model.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class BookService {
@Autowired
BookDao bookDao;
public List<BookInfo> getList () {
List<BookInfo> books = new ArrayList <>();
books = bookDao.mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1 ) {
book.setStatusCN("可借阅" );
} else {
book.setStatusCN("不可借阅" );
}
}
return books;
}
}
package com.example.project.dao;
import com.example.project.model.BookInfo;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Component
public class BookDao {
public List<BookInfo> mockData () {
List<BookInfo> books = new ArrayList <>();
for (int i = 0 ; i < 5 ; i++) {
BookInfo book = new BookInfo ();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 5 + 3 );
book.setPrice(new BigDecimal (new Random ().nextInt(100 )));
book.setPublish("出版社" + i);
book.setStatus(1 );
books.add(book);
}
return books;
}
}
可以看到在类的调用之间,我们是使用的注解,将类作为另一个类的成员。不用自己去 new 实例。
三、IoC 详解
3.1 Bean 的存储 Bean 在上面我们也说了,就是 Spring 管理起来的对象。
实现将对象交给 Spring 管理,共有两类注解类型可以:
类注解:@Controller、@Service、@Repository、@Component、@Configuration.
方法注解:@Bean.
3.2 @Controller(控制器存储) package com.example.springioc.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void hello () {
System.out.println("Hello" );
}
}
先获取 Spring 上下文对象
从 Spring 上下文中获取对象
package com.example.springioc.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserController userController = context.getBean(UserController.class);
userController.hello();
}
}
3.3 获取 Bean 对象 获取 Bean 对象主要是 ApplicationContext 类下的 getBean 方法,有下图中重载。
使用五大类注解让 Spring 管理 Bean 对象的默认取名方式如下 官方文档 :
将类名转换为小驼峰形式。UserController -》 userController
当前面是两个即多个大写字母连在一起,Bean 对象名就是类名。USController -》 USController
Bean 对象名也可以使用注解指定名称,在使用五大注解加上括号即可。栗子:@Controller("name")
package com.example.springioc.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserController bean1 = context.getBean(UserController.class);
bean1.hello();
UserController bean2 = (UserController) context.getBean("userController" );
bean2.hello();
UserController bean3 = context.getBean("userController" , UserController.class);
bean3.hello();
}
}
3.4 @Service(服务存储) 使用就加上 @Service 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
void print () {
System.out.println("do Service" );
}
}
3.5 @Repository(仓库存储) 使用就加上 @Repository 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
void print () {
System.out.println("do Repository" );
}
}
3.6 @Component(组件存储) 使用就加上 @Component 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
void print () {
System.out.println("do Component" );
}
}
3.7 @Configuration(配置存储) 使用就加上 @Configuration 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
void print () {
System.out.println("do Configuration" );
}
}
3.8 五大注解区别 @Controller @Service @Repository @Configuration 这四个注解都是 @Component 注解的衍生注解。
分这么多注解就是为了更好地分层(边界在使用中也没非常清晰):
@Controller 代表控制层。接收参数返回响应,控制层一定要使用 @Controller
@Service 代表服务层
@Repository 代表数据层
@Configuration 代表配置层
@Component 代表组件层
3.9 方法注解 @Bean package com.example.springioc.controller;
import com.example.springioc.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Bean
public User user () {
return new User ("zhangsan" , 11 );
}
public void hello () {
System.out.println("Hello" );
}
}
package com.example.springioc.controller;
import com.example.springioc.model.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
User bean1 = (User) context.getBean("user" );
System.out.println(bean1.getName());
}
}
使用 @Bean 注解默认方法名就是管理的 Bean 对象名。
@Bean 注解必须搭配五大类注解使用。
当方法有参数的时候,Spring 会从容器中根据参数类型去找,是否有这个类型的对象,如果没有,或者有多个不唯一都会报错,有唯一一个就会拿这个对象赋值。
@Bean 对象重命名可以直接加括号 @Bean("name1"),还可以使用 name 属性 @Bean(name = "name1"),还可以使用 value 属性 @Bean(value = "name1"),并且可以传 String 数组。
四、Spring 扫描路径 Spring 默认的扫描路径是启动类所在路径及其子路径。
当我们要扫描其它路径的时候,可以使用注解 @ComponentScan("需要扫描路径"),可以传数组。
其实不怎么用这个注解,直接启动类放在所有需要扫描的路径的最上层包下即可。
五、DI 详解 依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象。
属性注入 (Field Injection)
构造方法注入 (Constructor Injection)
Setter 注入 (Setter Injection)
5.1 属性注入 @Autowired 属性注入是使用 @Autowired 注解实现的
注意事项:
注入的对象必须是容器中已经有的,也就是使用五大类注解交给 Spring 管理的。
@Autowired 不能修饰 final 修饰的成员。
package com.example.springioc.controller;
import com.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService us;
public void hello () {
System.out.println("Hello" );
us.print();
}
}
package com.example.springioc;
import com.example.springioc.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserController bean = context.getBean(UserController.class);
bean.hello();
}
}
5.2 构造方法注入 package com.example.springioc.controller;
import com.example.springioc.service.UserService;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService us;
public UserController (UserService us) {
this .userService = us;
}
public void hello () {
System.out.println("Hello" );
us.print();
}
}
当只有一个构造函数的时候,直接可以注入。
当有两个及以上构造函数的时候,Spring 无法辨别使用哪一个构造函数注入,需要在使用的构造函数前加上 @Autowired 注解。
只能在一个构造方法上加上 @Autowired 注解。
5.3 setter 方法注入 直接加上 set 方法,加上 @Autowired 注解,将上面代码改成如下也可以使用。
package com.example.springioc.controller;
import com.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService us;
@Autowired
public void setUserService (UserService us) {
this .us = us;
}
public void hello () {
System.out.println("Hello" );
us.print();
}
}
set 方法必须加上 @Autowired 注解,可以给多个 set 方法使用注解。
不能修饰 final 修饰的成员的 set 方法。
属性注入
优点:简洁,使用方便;
缺点:只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使⽤的时候才会出现 NPE(空指针异常),不能注入一个 Final 修饰的属性
构造函数注入 (Spring 4.X 推荐)
优点:可以注入 final 修饰的属性,注入的对象不会被修改,依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法,通用性好,构造方法是 JDK 支持的,所以更换任何框架,他都是适用的
缺点:注入多个对象时,代码会比较繁琐
Setter 注入 (Spring 3.X 推荐)
优点:方便在类实例之后,重新对该对象进行配置或者注入
缺点:不能注入一个 Final 修饰的属性,注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险
5.4 @Autowired 注解问题及解决 当一个类交给 Spring 多个对象后,使用 @Autowired 注解,会无法分辨。
package com.example.springioc.service;
import com.example.springioc.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Bean
public User u1 (String name) {
return new User (name, 11 );
}
@Bean
public User u2 () {
return new User ("lisi" , 18 );
}
@Bean
public String name () {
return "zhangsan" ;
}
public void print () {
System.out.println("do Service" );
}
}
package com.example.springioc.controller;
import com.example.springioc.model.User;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Resource(name = "u1")
private User user;
public void hello () {
System.out.println("Hello" );
System.out.println(user.toString());
}
}
@Primary
@Qualifier
@Resource
使用 @Primary 注解:当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现。例如上面代码:
@Bean
@Primary
public String name () {
return "zhangsan" ;
}
使用 @Qualifier 注解:指定当前要注入的 bean 对象。在 @Qualifier 的 value 属性中,指定注入的 bean 的名称,必须与 @Autowired 一起用。例如上面代码:
@Autowired
@Qualifier("u1")
private User user;
使用 @Resource 注解:是按照 bean 的名称进行注入。通过 name 属性指定要注入的 bean 的名称。@Resource 是 JDK 提供的注解。
例如上面代码:
@Resource(name = "u2")
private User user;
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online