跳到主要内容 Spring IoC 与 DI 核心原理及 Bean 存储机制详解 | 极客日志
Java java
Spring IoC 与 DI 核心原理及 Bean 存储机制详解 Spring IoC 控制反转将对象创建权交给容器,实现解耦。依赖注入 DI 是 IoC 的具体实现,通过容器动态提供资源。文章通过造车案例对比传统开发与 IoC 开发模式,阐述高耦合问题及解决方案。重点介绍 Spring Bean 的存储方式,包括类注解如@Controller、@Service、@Repository、@Component 和@Configuration 的区别与应用场景。同时讲解 ApplicationContext 获取 Bean 实例的方法及 Bean 命名规则,帮助开发者理解 Spring 容器核心机制。
无尘 发布于 2026/2/5 更新于 2026/4/18 1K 浏览
一、Spring 是什么
Spring 框架就像一个帮你打理代码里'工具'的智能收纳盒,核心作用是让写程序变得更简单。平时写代码时,你要用到的各种'功能模块'(比如处理数据的组件、响应网页请求的组件),本来得自己一个个'造出来'(比如手动创建对象),还得费劲琢磨这些模块之间怎么配合——比如要造辆车,得自己先做轮子、再做底盘、最后拼车身,少一步都不行,改个轮子尺寸还得重新调整底盘和车身。但有了 Spring,你不用自己'造模块'了:只要告诉 Spring 哪些模块需要它管,它就会把这些模块存进'收纳盒'里。等你要用某个模块时,也不用自己去找,Spring 会主动把你需要的模块'递到手上'(这就是依赖注入)。要是某个模块要换版本、改功能,你也不用动其他代码,Spring 会帮你把新模块换好,不用你从头调整所有关联的部分。
1.1. 容器
'容器'泛指可容纳物品的工具,在 IT 领域特指一种轻量级、可移植的软件封装技术,将应用及其所有依赖(代码、库、配置文件等)打包,实现跨环境一致运行。
1.2. IoC
IoC 全称是控制反转,是 Spring 框架的核心思想,本质是把对象创建和管理的'控制权'从开发者编写的业务代码中,转移到了 Spring 容器手里。以前开发时,要用到某个对象(比如造汽车需要的轮子、底盘),得自己用 new 关键字手动创建,一旦底层对象有变化(比如轮子尺寸改了),所有依赖它的上层代码(底盘、车身、汽车)都得跟着改,代码耦合度很高。而有了 IoC,我们不用自己创建对象了,只需通过注解(比如 @Controller、@Service)告诉 Spring 哪些对象要交给它管理,Spring 启动时会自动创建这些对象并保存;等程序需要用某个对象时,也不用自己找,直接从 Spring 容器里获取(比如用 @Autowired 注入),哪怕底层对象变了,上层代码也不用修改,这样就解耦了代码,让程序更易维护。
以下是 Spring IoC 的官方文档:
二、IoC 介绍
如果我们现在有这么个需求,造一辆车。
2.1. 传统程序开发
按照传统程序开发的流程,我们想造一辆车,需要先造出车身,而车身需要依赖底盘,底盘又需要依赖轮子。
public class Main {
public static void main (String[] args) {
Car car = ();
car.run();
}
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 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
new
Car
public class Car {
private FrameWork frameWork;
public Car () {
this .frameWork = new FrameWork ();
System.out.println("Car init……" );
}
public void run () {
System.out.println("Car run……" );
}
}
public class FrameWork {
private Bottom bottom;
public FrameWork () {
this .bottom = new Bottom ();
System.out.println("FrameWork init……" );
}
}
public class Bottom {
public Tire tire;
public Bottom () {
this .tire = new Tire ();
System.out.println("Tire init……" );
}
}
public class Tire {
private int size = 20 ;
public Tire () {
System.out.println("The size of tire: " + size);
}
}
如果我们想给轮胎构造方法添加一个 size 参数,那么就需要把前面几个类里面的对象也需要修改,这就是'高耦合'。
2.2. 解决方案 在传统程序开发中,汽车(Car)、车身(Framework)、底盘(Bottom)、轮胎(Tire)存在严格的上层依赖下层关系,一旦最底层的轮胎(如尺寸)发生变化,整个调用链上的所有类都需同步修改,耦合度极高。针对这一问题,解决方案核心是倒置依赖关系并通过'注入传递'替代'内部创建'以实现解耦。具体而言,首先改变依赖逻辑,从传统的'根据轮胎设计底盘、根据底盘设计车身、根据车身设计汽车',转变为'先确定汽车整体需求,再依次推导车身、底盘、轮胎的设计要求',使依赖关系倒置为'轮胎依赖底盘、底盘依赖车身、车身依赖汽车';其次,摒弃每个类自行创建下级依赖对象的方式,改为在类的外部创建下级依赖对象后,通过传递(注入)的方式将其提供给当前类(例如,汽车类不再内部创建车身对象,而是通过构造函数接收外部已创建好的车身对象)。这种方式下,即使下级依赖类的创建逻辑发生变化(如参数增减),当前类也无需修改任何代码,彻底切断了类与类之间的强耦合关联,最终实现程序的灵活适配与易维护性提升。
public class Main {
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();
}
}
public class Tire {
private int size = 20 ;
public Tire (int size) {
this .size = size;
System.out.println("The size of tire: " + size);
}
}
public class Bottom {
public Tire tire;
public Bottom (Tire tire) {
this .tire = tire;
System.out.println("Tire init……" );
}
}
public class FrameWork {
private Bottom bottom;
public FrameWork (Bottom bottom) {
this .bottom = bottom;
System.out.println("FrameWork init……" );
}
}
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 run……" );
}
}
2.3. IoC 程序开发 在传统程序开发中,对象之间的依赖关系由开发者主动通过 new 关键字创建,例如上面'造汽车'的案例:Car 需要自行创建 FrameWork,FrameWork 再创建 Bottom,Bottom 又创建 Tire,形成'上层类控制下层类'的依赖链。这种模式下,若底层类(如 Tire 的尺寸需要修改)发生变化,整个依赖链上的所有类(Bottom、FrameWork、Car)都需同步修改,导致代码耦合度极高,维护成本高。
而 IoC 程序开发彻底反转了这种控制权:不再由使用方类创建依赖对象,而是由 IoC 容器统一负责所有对象(即 Spring 中的'Bean')的创建、初始化与生命周期管理。当应用程序中的某个类(如 Car)需要依赖其他对象(如 FrameWork)时,无需自行创建,只需通过依赖注入的方式(如构造函数注入、属性注入、Setter 注入),由 IoC 容器将预先创建好的依赖对象'注入'到当前类中。例如文档中改造后的造车案例,IoC 容器先创建 Tire 对象,再将其注入 Bottom,Bottom 注入 FrameWork,FrameWork 最终注入 Car,此时即便 Tire 的实现逻辑修改,Bottom、FrameWork、Car 等上层类无需任何调整,彻底解决了传统开发的高耦合问题。
2.4. IoC 优势 IoC 实现了资源的集中化管理,减少重复开发与配置成本。IoC 容器作为第三方管理者,统一负责对象(Bean)的创建、初始化、生命周期管控与配置,开发者无需在代码中重复编写 new 对象的逻辑,也无需关注对象创建的细节。
并且,IoC 提升了代码的灵活性与通用性,适配更多开发场景。一方面,依赖关系的'倒置'让程序设计更符合'开闭原则'——新增功能时,只需在容器中新增 Bean 配置,现有业务逻辑无需修改,即可通过容器注入新的依赖实例;另一方面,IoC 的实现不依赖特定框架,例如构造方法注入基于 JDK 原生规范,即便更换开发框架,核心的依赖注入逻辑仍可复用,降低了技术选型的约束成本。
三、DI 介绍 Spring DI(Dependency Injection,依赖注入)是 Spring 框架核心思想 IoC(控制反转)的具体实现,其核心是容器在运行期间动态为应用程序提供运行时所依赖的资源(通常是对象) ,从而实现对象之间的解耦,让开发者无需手动创建依赖对象,专注于业务逻辑开发。
IoC 是一种设计思想,强调'对象的控制权由开发者转移到 Spring 容器',DI 是 IoC 思想的落地实现,从'应用程序视角'描述——当应用程序需要某个依赖对象时,容器主动将该对象'注入'到应用程序中,而非应用程序自行创建。
四、Bean 的存储
类注解:@Controller、@Service、@Repository、@Component、@Configuration.
方法注解:@Bean
4.1. @Controller(存储器存储) package com.yang.test1_22_1.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void hello () {
System.out.println("hello userController" );
}
}
package com.yang.test1_22_1;
import com.yang.test1_22_1.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1221Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1221Application.class, args);
UserController bean = context.getBean(UserController.class);
bean.hello();
}
}
这里的 ApplicationContext(应用上下文)是 Spring 框架的核心接口之一,属于 IoC(控制反转)容器的高级形态 。这个'应用上下文'可以理解为当程序正在运行时,'围绕着程序转的所有东西'——包括程序里的各种'工具'(Bean)、工具之间的搭配规则、程序需要的配置等。
除了上述方式,还有其他的方式可以获取 Bean 实例。
方法 描述 返回值 getBean(Class<T> requiredType) 如果存在,返回唯一匹配给定对象类型的 bean 实例。 <T> T getBean(String name) 返回指定 bean 的实例,该实例可以是共享的,也可以是独立的。 Object getBean(String name, @Nullable object ... args) 返回指定 bean 的实例,该实例可以是共享的,也可以是独立的。 Object
package com.example.test1_25_1;
import com.example.test1_25_1.user.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1251Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1251Application.class, args);
UserController bean = context.getBean(UserController.class);
bean.setName("Yang" );
bean.hello();
UserController userController1 = (UserController) context.getBean("userController" );
userController1.hello();
UserController userController2 = context.getBean("userController" , UserController.class);
userController2.hello();
System.out.println(bean);
System.out.println(userController1);
System.out.println(userController2);
}
}
我们会发现 3 个对象的内存地址的 16 进制的结果是一样的,这个其实也是一种实现单例模式的思想。
Spring Bean 命名核心规则:每个 Bean 拥有一个或多个标识符(主名 + 别名),所有标识符在托管该 Bean 的 IoC 容器中必须唯一 ;通常一个 Bean 仅需一个主标识符,多个标识符时额外的视为别名。
通用命名约定:遵循Java 实例字段的驼峰命名法 ,首字母小写,后续单词首字母大写,如 accountManager、userDao、loginController;统一命名可提升配置可读性,也便于 Spring AOP 按名称批量匹配 Bean。
自动命名规则:类路径组件扫描时,容器会为未显式命名 的组件自动生成 Bean 名,规则为:取类的简单类名 ,并按 java.beans.Introspector.decapitalize 规则转换:常规情况下,将简单类名首字母转为小写;特殊情况下,若类名前两个字符均为大写 ,则保留原始大小写。
public static String decapitalize (String name) {
if (name == null || name.length() == 0 ) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1 )) && Character.isUpperCase(name.charAt(0 ))){
return name;
}
char [] chars = name.toCharArray();
chars[0 ] = Character.toLowerCase(chars[0 ]);
return new String (chars);
}
如果我们把上面传入的参数改为"UserController",再次运行程序,就会出现"No bean named 'UserController' available"的异常,这就是因为 Spring 容器在注册 Bean 时,默认会使用类名首字母小写 作为 Bean 的名称。
还有一种错误,如果我们忘加了 @Controller 注解,就会出现"No qualifying bean of type 'com.example.test_1_25_1.user.UserController' available"的异常,这就是没有将 UserController 实例化作为 Bean 管理起来。
4.2. @Service(服务存储) package com.yang.test1_25_2.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void hello () {
System.out.println("Hello UserService..." );
}
}
package com.yang.test1_25_2;
import com.yang.test1_25_2.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1252Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1252Application.class, args);
UserService bean = context.getBean(UserService.class);
bean.hello();
}
}
4.3. @Component(组件存储) package com.yang.test1_25_3.component;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void hello () {
System.out.println("Hello UserComponent..." );
}
}
package com.yang.test1_25_3;
import com.yang.test1_25_3.component.UserComponent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1253Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1253Application.class, args);
UserComponent bean = context.getBean(UserComponent.class);
bean.hello();
}
}
4.4. @Repository(仓库存储) package com.yang.test1_25_4.repository;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void hello () {
System.out.println("Hello UserRepository" );
}
}
package com.yang.test1_25_4;
import com.yang.test1_25_4.repository.UserRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1254Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1254Application.class, args);
UserRepository bean = context.getBean(UserRepository.class);
bean.hello();
}
}
4.5. @Configuration(配置存储) package com.yang.test1_25_5.configuration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
public void hello () {
System.out.println("Hello UserConfiguration..." );
}
}
package com.yang.test1_25_5;
import com.yang.test1_25_5.configuration.UserConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1255Application {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(Test1255Application.class, args);
UserConfiguration bean = context.getBean(UserConfiguration.class);
bean.hello();
}
}
4.6. 为什么需要多类注解 这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途。
@Controller:控制层,接收请求,对请求进行处理,并进行响应;
@Service:业务逻辑层,处理具体的业务逻辑;
@Repository:数据访问层,也称为持久层。负责数据访问操作;
@Configuration:配置层,处理项目中的一些配置信息;
@Component:组件。
这就像车牌号一样,车牌号是唯一的,用于标识一辆车。不同的省之间,车牌号的第一位的省份缩写不一样,紧跟的第二位字母也会因各省的地区不同。这样既可以节约号码,又可以直接了解这辆车的所属地。
同样地,在一些企业当中,分为表现层、业务逻辑层、数据层。同样可以根据不同的注解,提高源码的可读性,又能精准定位问题所在。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value () default "" ;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class) String value () default "" ;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class) String value () default "" ;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class) String value () default "" ;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class) String value () default "" ;
boolean proxyBeanMethods () default true ;
@Deprecated(since = "7.0") boolean enforceUniqueMethods () default true ;
}
我们会发现,除了 @Component 注解,其余的 4 个注解都依托于 @Component,是 @Component 的一个元注解,这些注解也被称为 @Component 的衍生注解。
本文主要讲解了 Spring IoC 的基础概念与 Bean 注解的使用,后续将继续深入探讨 Bean 的生命周期等内容。