为什么要告别 new 对象?
在传统的 Java 开发中,如果类 A 依赖于类 B,我们通常会在 A 的构造函数或方法中直接 B b = new B()。这种方式看似简单,但在大型项目中会引发"强耦合"灾难:
-
牵一发而动全身 :一旦类 B 的构造函数发生变化,所有
new过 B 的类都必须同步修改。 -
资源管理混乱:开发者需要手动管理成千上万个对象的生命周期,极易造成内存泄漏或重复创建。
比如假如我们想要来创建一个car汽车实例,但是汽车的创建又依赖车身 车身依赖底盘,底盘依赖轮胎 如下:

如此一来,我们在创建car对象时,就需要创建出多个对象,比如:
java
public class NewCarExample {
public static void main(String[] args) {
//创建car实例
Car car = new Car();
car.run();
}
}
public class Car {
//车身
private static Framework framework;
public Car(){
//构建底盘
framework = new Framework();
System.out.println("car init...");
}
//启动汽车car
public void run(){
System.out.println("car is running");
}
}
public class Framework {
//底盘
private static Bottom bottom;
public Framework (){
bottom = new Bottom();
System.out.println("framework init...");
}
}
public class Bottom {
//轮胎
private static Tire tire;
public Bottom(){
tire = new Tire();
System.out.println("bottom init...");
}
}
public class Tire {
//轮胎的尺寸
private int size;
public Tire(){
this.size = 20;
System.out.println("轮胎的尺寸: " + size);
}
}
点击运行,可以发现car实例已经成功被创建,其子对象也已被创建

但是也不难发现一个问题,我们在上面的代码中是把轮胎的尺寸给写死了的,如果想要自定义一个轮胎的尺寸的时候,就要在new Car()的时候进行size传参,但是一处的修改必然会带来其余类对象的修改,这时候就需要修改Car依赖的所有对象的代码,添加带有轮胎尺寸的相应构造方法.
只是要求实例化Car时,自定义对象的轮胎尺寸就需要修改四个实体类的代码细节,这也太麻烦了,就更不必说更多,更复杂的业务需求了.
有什么办法可以解决这个问题吗?自然是有的
Spring 的出现,本质上是将对象的"控制权"从开发者手中上缴给框架。这便引出了 Spring 的灵魂------IoC 容器。
1. 概念审计:IoC 与 DI 的本质区别
为了消除理解歧义,我们必须界定这两个核心术语的边界:
| 术语 | 全称 | 核心定义 | 形象比喻 |
|---|---|---|---|
| IoC | 控制反转 (Inversion of Control) | 一种设计思想。将对象的创建、配置与生命周期管理从程序代码转移到外部容器。 | 行政审批中心:你不再自己盖房子(建对象),而是向中心申请,由中心统一分配。 |
| DI | 依赖注入 (Dependency Injection) | IoC 的具体实现手段。容器在运行期间,动态地将依赖对象注入到组件中。 | 物流配送:你声明需要某个工具,物流公司(Spring)直接送到你家门口。 |
结论 :Spring 是一个包含了众多工具方法的 IoC 容器。
要理解这句话,必须先解构其中的两个关键缩略语:IoC 与 容器。
1.1 什么是容器 (Container)?
在程序设计中,容器是指用来"装"对象(Object)的东西。
-
传统开发 :你自己
new对象,你自己管理对象的生命周期(创建、使用、销毁)。比如上面我们就需要层层创建car ,framework,bottom等多个实例,自己管理new对象 -
Spring 容器:像一个智能仓库。你只需要告诉 Spring 你需要什么,它负责把对象实例化并存放在仓库中,随取随用。
1.2 什么是 IoC (Inversion of Control)?
控制反转 (IoC) 是一种设计思想。
| 维度 | 传统模式 (Control) | IoC 模式 (Inverted) |
|---|---|---|
| 对象的创建权 | 程序员手动 new(主动权在代码) |
由 Spring 框架创建(主动权在框架) |
| 依赖关系管理 | 类内部直接写死依赖 | 由外部注入依赖(DI) |
| 耦合度 | 高耦合,改一个类可能崩一串 | 低耦合,模块间互不干扰 |
1.3 基于IoC(控制反转)思想重新实现car实例创建
有了先前的铺垫,我们就来基于IoC来重新创建car实例
java
public class IocCarExample {
public static void main(String[] args) {
Tire tire = new Tire(15);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
public class Car {
private Framework framework;
public Car(Framework framework){
this.framework = framework;
System.out.println("car init...");
}
public void run(){
System.out.println("car is running...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom){
this.bottom = bottom;
System.out.println("framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom (Tire tire) {
this.tire = tire;
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
public Tire(int size){
this.size = size;
System.out.println("tire init...");
}
}
可以看到,使用Ioc思想来创建Car对象时,我们的创建顺序从传统开发的car -> tire,变成了 tire- > car .并且对象的创建时机也有所不同.传统开发是在创建car对象时还要new其依赖的底层对象,但是Ioc则是让依赖对象提前创建好, 在new car实例的时候自动注入即可
这本质上是将资源的控制权交给了第三方。作为开发者,我们不再关注"对象怎么来",而只关注"对象怎么用"。这种"甩手掌柜"的思想,正是构建大型复杂系统的基石
2. Spring 体系结构:全家桶的关系图谱
在实际开发中,Spring、Spring MVC 与 Spring Boot 经常被混淆,其逻辑层次如下:
-
Spring Framework:基石。提供 IoC 和 DI 的核心功能(Spring Core)。
-
Spring MVC:基于 Spring 构建的 Web 框架,专注于解决 HTTP 请求处理。
-
Spring Boot:脚手架。通过"约定大于配置"简化了 Spring 应用的搭建过程,它可以快速集成其他框架(如 Mybatis、JPA)。

可以概括为:
"Spring Boot 是你的'装修队长'。它把作为'水泥和钢筋'的 Spring Framework,以及作为'水电管道'的 Spring MVC 快速组装好,让你能直接搬进'毛坯房'进行业务装修。"
3. 实战指南:Spring IoC 的存储与取用
3.1 Bean 的存储(五大注解)
在类上添加以下注解,即可将其交给 Spring 管理,也就是在类加载的时候自动为我们创建对应的对象,并且把bean存储在Spring容器中:
-
@Controller:控制层,处理 Web 请求。 -
@Service:服务层,处理业务逻辑。 -
@Repository:持久层,处理数据库操作。 -
@Component:通用组件。 -
@Configuration:配置类。
java
@Controller
public class UserController {
public static void sayHello(){
System.out.println("hello UserController");
}
}
//验证对象是否创建
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.sayHello();
}
}

如果不再对应的类中添加注解,尝试从Spring容器中获取Bean 对象就会出现报错,

@component 元注解

提供了这么多的注解,是否存在差异?我们可以通过查看注解的源码来观察细节,
这里以@Service和 @Controller这两大注解来观察,可以看到这两大注解之上都存在了一个共同的注解@component ,可以理解为component时其他注解的父注解 ,其余注解则是@component的子注解,这一点有点像面向对象的继承关系,但是继承的特点是父类的功能子类都实现,但是子类的功能父类不一定有,目的是为了方便程序员拓展功能,
但是@component在功能上和其余注解并无不同,了解即可
以上五大注解在使用的时候没有什么不同,本质都是把类交给Spring去管理,创建出对应的Bean,实际使用时选择自己喜欢的即可
3.2 Bean的创建时机
既然我们可以在类前通过加注解的方式来管理Bean,那Bean是什么时候创建并 添加到Spring容器中的.?
- 创建时机 : Bean的创建时机是在Srping容器初始化的时候创建的,Spring 容器启动(refresh)时 → 调用构造方法,实例化对象
- 创建方式 : Spring不直接显式的调用 new来实例Bean ,而是通过反射的方式去实例Bean
3.3 Bean 的命名规范
Spring 在存储 Bean 时有默认的命名约定:
-
常规情况 :类名首字母小写(例:
MessageInfo-> `messageInfo)。 -
特殊情况 :如果类名前两位字母均是大写,则 Bean 名称为原类名(例:
IUserService->IUserService)。
Bean的重命名
如果不想使用Spring为我们的
3.4 依赖注入 (DI) 的三种实现方式
DI(Dependency Injection)依赖注入 ,就是 Spring 帮你自动把需要的 Bean 赋值给属性,不用自己 new,不用自己 getBean()。
可以理解为:依赖注入的本质就是把Spring的Bean对象进行赋值给属性
1. 字段注入(最简洁)
直接在属性上加 @Autowired
java
@Service
public class UserService {
//属性注入
@Autowired
private UserController userController;
public void sayHello(){
System.out.println("Hello UserService!");
}
}
实现原理 :Spring 通过反射 拿到字段,直接赋值。
缺点: 无法对final修饰的字段进行注入
为什么?
-
final 修饰的成员变量,必须在 声明时赋值或 构造方法里赋值一旦对象创建完成,就不能再修改**。
-
字段注入的时机,先执行构造方法 → 对象创建完成,之后 Spring 才通过反射给字段赋值, 这时候 final 字段已经 "不可变" 了
2. 构造器注入(Spring 官方推荐)
java
@Service
public class UserService {
private final UserController userController;
//构造器注入
@Autowired
public UserService(UserController userController){
this.userController = userController;
}
public void sayHello(){
System.out.println("Hello UserService!");
}
}
优点:
- 依赖不可变,可加
final - 避免循环依赖问题
- 更利于单元测试

3. Setter 方法注入
java
@Controller
public class UserController2 {
private UserService userService;
//注⼊⽅法3: Setter⽅法注⼊
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHello(){
System.out.println("hi,UserController3...");
userService.sayHello();
}
}
适用:可选依赖,可动态替换。
4.不可循环注入
如果我们尝试把两个字段进行循环注入时,会出现什么情况呢? 不妨我们尝试一下

此时直接运行启动类,会出现这样的报错

为什么会出现这种情况呢? 这是因为出现了循环依赖
好比:A 依赖 B,B 依赖 A → 互相等着对方先创建 → 逻辑死锁
这一块有点类似于多线程的死锁, 好比车钥匙锁家里了,但是家门钥匙在车里 .双方行为的启动都受到循环依赖双方的约束 , 导致了死锁
所以我们要尽量避免出现循环依赖的情况发生
5.循环注入的解决方案(@Lazy)
如果出现了循环注入,我们可以添加@Lazy注解来解决
循环注入的原因是Bean对象同一时刻互相等待对方的资源,那我们让某一方Bean提前注入不就可以了吗,
我们可以在其中一个 Bean 的依赖注入点(构造器参数、字段或 Setter 方法)上添加 @Lazy 注解。这会告诉 Spring 不要立即注入真实的 Bean,而是注入一个代理对象。只有当第一次真正调用该 Bean 的方法时,代理对象才会去获取并初始化真实的 Bean,从而打破初始化时的循环。
添加@Lazy的代码:
java
@Controller
public class UserController {
//属性注入
@Autowired
@Lazy private UserService userService;
public void sayHello(){
System.out.println("hello UserController");
}
}
@Service
public class UserService {
private final UserController userController;
//构造器注入
@Autowired
public UserService(@Lazy UserController userController){
this.userController = userController;
}
public void sayHello(){
System.out.println("Hello UserService!");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//通过名称获取Bean对象
UserService userService = (UserService) context.getBean("userService");
userService.sayHello();
UserController userController = context.getBean(UserController.class);
userController.sayHello();
}
}
此时运行启动类,可以看到能观察到运行结果

3.5 Bean 的创建流程
1. Spring 启动
加载配置,准备开始初始化 IOC 容器。
2. 扫描注解类
扫描 @Controller / @Service / @Repository / @Component 标记的类,找到要交给 Spring 管理的类。
3. 保存类信息(不创建对象)
把这些类的信息封装成 BeanDefinition,存在 Spring 工厂里,只登记、不实例化。
4. 容器刷新 refresh ()
Spring 容器真正开始工作:
- 准备 Bean 工厂
- 注册后置处理器(
@Autowired、AOP 靠它生效) - 准备实例化所有单例 Bean
5. 反射创建实例
遍历 BeanDefinition,反射调用构造方法,创建出原始对象。
6. 填充属性(依赖注入)
通过 @Autowired 等注解,把依赖的 Bean 赋值进来,对象变完整。
7. 放入单例池
把创建好的 Bean 存入 singletonObjects 单例池,后续直接从这里获取。

4. 总结与思考
4.1 技术总结
-
容器化思维:Spring 就像一个巨大的工厂,开发者只需通过注解(元数据)提供"图纸",工厂负责生产和配送。
-
解耦价值:DI 实现了对象间的"松耦合",使得代码更易于测试(Mock 测试)和维护。
4.2 深度思考
学习完 IoC 和 DI 后,我们需要思考一个进阶问题:"如果所有对象都交给 Spring 管理,是否会造成性能负担?"
实际上,Spring 默认使用单例模式 (Singleton) 管理 Bean,这意味着在容器生命周期内,每个类只有一个实例。这不仅节约了内存,还通过预加载机制提高了运行效率。然而,这也带来了线程安全的挑战。作为开发者,在享受 DI 带来的便利时,必须时刻警惕在 Service 层使用有状态的成员变量。
本文有关Spring IoC 和DI ,Bean的相关介绍就到这里了,如有纰漏还请指出