[JavaEE]深度解构 Spring 核心:从控制反转 (IoC) 到依赖注入 (DI) 的架构演进


为什么要告别 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容器中:

  1. @Controller:控制层,处理 Web 请求。

  2. @Service:服务层,处理业务逻辑。

  3. @Repository:持久层,处理数据库操作。

  4. @Component:通用组件。

  5. @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 技术总结

  1. 容器化思维:Spring 就像一个巨大的工厂,开发者只需通过注解(元数据)提供"图纸",工厂负责生产和配送。

  2. 解耦价值:DI 实现了对象间的"松耦合",使得代码更易于测试(Mock 测试)和维护。

4.2 深度思考

学习完 IoC 和 DI 后,我们需要思考一个进阶问题:"如果所有对象都交给 Spring 管理,是否会造成性能负担?"

实际上,Spring 默认使用单例模式 (Singleton) 管理 Bean,这意味着在容器生命周期内,每个类只有一个实例。这不仅节约了内存,还通过预加载机制提高了运行效率。然而,这也带来了线程安全的挑战。作为开发者,在享受 DI 带来的便利时,必须时刻警惕在 Service 层使用有状态的成员变量。

本文有关Spring IoC 和DI ,Bean的相关介绍就到这里了,如有纰漏还请指出

相关推荐
立莹Sir2 小时前
【架构图解+实战配置】SaaS多租户资源隔离的云原生完整方案
云原生·架构
毅炼2 小时前
MySQL常见问题总结(2)
java·数据库·mysql
庞轩px2 小时前
第二篇:String、StringBuilder、StringBuffer深度剖析
java·字符串·stringbuilder·string·stringbuffer·字符串常量池
色空大师2 小时前
【阿里云部署服务问题指南】
java·mysql·阿里云·docker
Rsun045512 小时前
9、Java 外观模式从入门到实战
java·开发语言·外观模式
清心歌2 小时前
TreeSet 深度解析
java·开发语言
迷藏4942 小时前
**RISC-V生态下的嵌入式开发新范式:从指令集到自定义外设的全流程实战**在当前国产化
java·python·risc-v
小松加哲2 小时前
Tomcat 核心原理全解析(含请求流转+组件源码+多应用配置)
java·tomcat·firefox
Lyyaoo.2 小时前
【JAVA基础面经】juc包(java.util.concurrent)
java·开发语言