一、IoC 与 DI
名词解释:
- Spring 是一个装了众多工具对象的 IoC 容器。
- IoC 思想:对象交给 Spring 管理,就是 IoC 思想。
- IoC:Inversion of Control,控制反转。
本文详解 Spring IoC(控制反转)与 DI(依赖注入)的核心概念。通过对比传统对象创建与容器管理,阐述降低耦合度的优势。讲解 Bean 存储注解(@Controller, @Service 等)及获取方式。分析属性、构造器、Setter 三种依赖注入方式的优缺点,并提供多 Bean 注入冲突的解决方案(@Primary, @Qualifier, @Resource)。

名词解释:
控制权反转,需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器(IoC 容器。Spring 是一个 IoC 容器,所以有时 Spring 也称为 Spring 容器),程序中只需要依赖注入 (Dependency Injection, DI) 就可以了。
实现下面的需求:
在传统的实现中,我们将每个模块当成一个类:
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 Bottom();
System.out.println("Framework init...");
}
}
/**
* 底盘类
*/
static class Bottom {
private Tire tire;
public Bottom() {
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺寸
private int size;
public Tire() {
this.size = 17;
System.out.println("轮胎尺寸:" + size);
}
}
}
但是如上面的代码,如果我们要修改一个参数,会导致整个调用链都跟着修改。
我们为解决上面耦合度过高,可以采取: 把由自己创建的下级类,改为传递的方式(也就是注入的方式), 每次调整只需要调整对应那个类的代码即可。 这样无论底层类如何变化,整个调用链是不用做任何改变的。
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);
}
}
}
DI: Dependency Injection(依赖注入) 容器在运行期间,动态地为应用程序提供运行时所依赖的资源,称之为依赖注入。
就像上面调用关系中:
Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能: • 存 • 取 Spring 容器管理的主要是对象,这些对象,我们称之为 "Bean"。我们把这些对象交由 Spring 管理,由 Spring 来负责对象的创建和销毁。我们程序只需要告诉 Spring,哪些需要存,以及如何从 Spring 中取出对象
我们实现这样的功能,主要靠两个注解:
像把前面的图书管理系统的 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();
}
}
BookService 类:
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;
}
}
BookDao 类:
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 实例。
Bean 在上面我们也说了,就是 Spring 管理起来的对象。
实现将对象交给 Spring 管理,共有两类注解类型可以:
先使用 @Controller 将类存储:
package com.example.springioc.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void hello() {
System.out.println("Hello");
}
}
从 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) {
// 先获取 Spring 上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
// 从 Spring 上下文中获取对象
UserController userController = context.getBean(UserController.class);
userController.hello();
}
}
获取 Bean 对象主要是 ApplicationContext 类下的 getBean 方法,有下图中重载。
使用五大类注解让 Spring 管理 Bean 对象的默认取名方式如下 官方文档:
UserController -》 userControllerUSController -》 USControllerBean 对象名也可以使用注解指定名称,在使用五大注解加上括号即可。栗子:@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();
}
}
使用就加上 @Service 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
void print() {
System.out.println("do Service");
}
}
使用就加上 @Repository 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
void print() {
System.out.println("do Repository");
}
}
使用就加上 @Component 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
void print() {
System.out.println("do Component");
}
}
使用就加上 @Configuration 注解,拿到 Bean 对象方法不变。
package com.example.springioc.service;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
void print() {
System.out.println("do Configuration");
}
}
@Controller @Service @Repository @Configuration 这四个注解都是 @Component 注解的衍生注解。
分这么多注解就是为了更好地分层(边界在使用中也没非常清晰):
使用:
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("name1"),还可以使用 name 属性 @Bean(name = "name1"),还可以使用 value 属性 @Bean(value = "name1"),并且可以传 String 数组。
Spring 默认的扫描路径是启动类所在路径及其子路径。
当我们要扫描其它路径的时候,可以使用注解 @ComponentScan("需要扫描路径"),可以传数组。
其实不怎么用这个注解,直接启动类放在所有需要扫描的路径的最上层包下即可。
依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象。
依赖注入,Spring 给我们提供了三种方式:
属性注入是使用 @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 {
@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();
}
}
打印结果为 Hello do Service
直接使用构造函数,将上面代码改成如下也可以使用。
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();
}
}
注意事项:
直接加上 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();
}
}
注意事项:
优缺点比较:
属性注入
构造函数注入 (Spring 4.X 推荐)
Setter 注入 (Spring 3.X 推荐)
当一个类交给 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());
}
}
报错信息:
解决方法: 提供了以下几种注解解决:
@Bean
@Primary
public String name() {
return "zhangsan";
}
@Autowired
@Qualifier("u1")
private User user;
@Resource(name = "u2")
private User user;
@Autowired 工作流程

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online