1. IoC
1.1 容器
容器是用来容纳某种物品的(基本)装置。——来自:百度百科
生活中的水杯,垃圾桶,冰箱等等,都是容器
代码中的容器,如:List/Map -> 数据存储容器;Tomcat -> Web 容器
1.2 IoC
IoC 是 Sping 的核心思想,前面在类上面添加 @RestController 和 @Controller 注解,就是把这个对象交给 Sping 管理,Spring 框架启动时,就会加载该类,把对象交给 Spring 管理,就是 IoC 思想
IoC:Inversion of Control(控制反转),也就是说 Spring 是一个 “控制反转” 的容器
控制反转就是控制权反转,也就是获得依赖对象的过程被反转了
当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入(Dependency Injection,DI)就可以了
这个容器称为:IoC 容器,Spring 是一个 IoC 容器,所以有时 Spring 也称为 Spring 容器
控制反转是一种思想,在生活中也是处处体现
⽐如⾃动驾驶, 传统驾驶⽅式,⻋辆的横向和纵向驾驶控制权由驾驶员来控制,现在交给了驾驶⾃ 动化系统来控制,这也是控制反转思想在⽣活中的实现.
⽐如招聘,企业的员⼯招聘,⼊职,解雇等控制权,由⽼板转交给给HR(⼈⼒资源)来处理
1.3 IoC 应用
现有需求:造一辆车
1.3.1 传统程序开发
实现思路:
先设计轮子(Tire),然后根据轮子大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car)
这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子
实现代码:
1.3.2 问题分析
此时用户说需要生成不同尺寸的轮胎,就需要传参
可以看出程序的问题是:当最底层代码改动之后,整个调用链上的所有代码都需要修改
程序的耦合度非常高
1.3.3 解决方案
在上⾯的程序中,我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改.同样因 为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改,也就是整个设计⼏乎都得改
我们尝试换⼀种思路,我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦.这时候,依赖关系就倒置过来了:轮⼦依赖底盘,底盘依赖⻋⾝, ⻋⾝依赖汽⻋
1.3.4 IoC 程序
经过以上调整,无论底层类如何变化,整个调用链不用做任何改变,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了
1.3.5 IoC 优势
在传统的代码中对象创建顺序是:Car->Framework->Bottom->Tire
改进之后解耦的代码的对象创建顺序是:Tire->Bottom->Framework->Car
我们发现了⼀个规律,通⽤程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了 Framework,Framework创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由 当前类控制了.
这样的话,即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是IoC的实现思想
上面就是控制反转的示例,而控制反转容器(IoC 容器)如下图所示
上面容器内的代码就是 IoC 做的工作
从上⾯也可以看出来,IoC容器具备以下优点:
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集 中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度
- 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等),我们需要使⽤时,只需要从IoC容器中去取就可以了
- 我们在创建实例的时候不需要了解其中的细节,降低了使⽤资源双⽅的依赖程度,也就是耦合度
Spring 就是一种 IoC 容器,帮助我们来做了这些资源管理
1.4 DI 介绍
DI:Dependency Injection(依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入
程序运⾏时需要某个资源,此时容器就为其提供这个资源
从这点来看, 依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,依赖注⼊是 从应⽤程序的⻆度来描述,就是指通过引⼊IoC容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解 耦
上述示例中,是通过构造函数的方式,把依赖对象注入到需要使用的对象中的
IoC 是一种思想,也是“目标”,而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现,所以也可以说 DI 就是 IoC 的一种实现
2. IoC & DI 使用
既然 Spring 是一个 IoC(控制反转) 容器,作为容器,它就具备两个最基础的功能:存 和 取
Spring 容器管理的主要是对象,这些对象被称为 “Bean”,我们把这些对象交由 Spring 管理,由 Spring 来负责对象的创建和销毁,我们的程序代码只需要告诉 Spring 哪些需要存,以及如何从 Spring 中取出对象
以前面写的图书管理系统为例:
目标:把 BookDao、BookService 交给 Spring 管理,完成 Controller 层,Service 层,Dao 层的解耦
步骤:
- Service 层及 Dao 层的实现类,交给 Spring 管理,使用注解 @Component
- 在 Controller 层和 Service 层注入运行时依赖的对象,使用注解 @Autowired
实现:
1) 把 BookDao 交给 Spring 管理,由 Spring 来管理对象
2) 把 BookService 交给 Spring 管理,由 Spring 来管理对象
3) 删除创建 BookDao 的代码,从 Spring 中获取对象
4) 删除创建 BookService 的代码,从 Spring 中获取对象
5) 使用 Postman 测试
3. IoC 详解
3.1 Bean 的存储
共有两类注解类型可以实现
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean
3.1.1 @Controller(控制器存储)
在传统代码中:
想在 main 中调用 UserController 中的 say 方法需要如下图
使用 @Controller 存储 bean 的代码如下所示:
当我们将 @Controller 注释掉后:
ApplicationContext 翻译过来就是:Spring 上下文
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文
获取 bean 对象的其他方式
上述时根据类型来查找对象,ApplicationContext 也提供了其他获取 bean 的方式,ApplicationContext 获取 bean 对象的功能,是父类 BeanFactory 提供的功能
public interface BeanFactory {// 以上省略...// 1. 根据 bean 名称获取 beanObject getBean(String var1) throws BeansException;// 2. 根据 bean 名称和类型获取 bean<T> T getBean(String var1, Class<T> var2) throws BeansException;// 3. 按 bean 名称和构造函数参数动态创建 bean,只适用于具有原型(prototype)作用域的 beanObject getBean(String var1, Object... var2) throws BeansException;// 4. 根据类型获取 bean<T> T getBean(Class<T> var1) throws BeansException;// 5. 按 bean 名称和构造函数参数动态创建 bean,只适用于具有原型(prototype)作用域的 bean<T> T getBean(Class<T> var1, Object... var2) throws BeansException;// 以下省略...
}
常用的是上述 1、2、4 三种方式,获取到的 bean 都是一样的
其中 1、2 都涉及到根据名称来获取对象,在 Spring 中会给每个对象起一个名字,根据 Bean 的名称(BeanId)就可以获取到对应的对象(类似于每一个学生都有唯一的学号)
第 4 种方式只适合该类只有一个对象的情况
Bean 命名约定:
官方文档说明:BeanOverview::SpringFramework
程序开发人员不需要为 bean 指定名称(BeanId),如果没有显式的提供名称(BeanId),Spring 容器将为该 bean 生成唯一的名称
命名约定使用 Java 标准约定作为实例字段名,也就是说,bean 名称以小写字母开头,然后使用驼峰式大小写
比如:
类名:UserController,Bean 的名称为:userController
类名:AccountManager,Bean 的名称为:accountManager
类名:AccountService,Bean 的名称为:accountService
也有一些特殊情况,当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写,这些规则与 java.beans.Introspector.decapitalize(Spring 在这里使用的)定义的规则相同
比如:
类名:UController,Bean 的名称为:UController
类名:AManager,Bean 的名称为:AManager
根据这个命名规则来获取 Bean:
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 SpringIoCApplication {public static void main(String[] args) {// 获取 Spring 上下文对象ApplicationContext context = SpringApplication.run(SpringIoCApplication.class, args); // ApplicationContext可以理解为 Spring 的容器// 方法一// 从 Spring 上下文中获取对象UserController bean = context.getBean(UserController.class);// 使用对象bean.say();// 方法二UserController userController = (UserController)context.getBean("userController");userController.say();// 方法三UserController userController1 = context.getBean("userController", UserController.class);userController1.say();}
}
运行结果:
打印这三个对象:
地址一样,说明对象是同一个(单例模式)
3.1.2 其他注解演示:
3.2 为什么这么多类注解
这个也是和前面说的应用分层是呼应的,让程序员看到类注解之后,就能直接了解当前类的用途
- @Controller:控制层,接收请求,对请求进行处理,并进行响应
- @Service:业务逻辑层,处理具体的业务逻辑
- @Repository:数据访问层,也成为持久层,负责数据访问操作
- @Configuration:配置层,处理项目中的一些配置信息
程序的应用分层调用流程如下:
类注解之间的关系
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码:
其实这些注解里面都有一个注解 @Component,说明它们本身就属于注解 @Component 的“子类”
@Component 是一个元注解,可以用来注解其他类注解
@Controller、@Service、#Repository 等,这些注解被称为 @Component 的衍生注解
3.3 方法注解 @Bean
类注解是添加到某个类上的,但是存在两个问题:
- 使用外部包里的类,没办法添加类注解
- 一个类,需要多个对象,比如:多个数据源
上面场景下,就需要使用方法注解 @Bean
下面假设一个 UserInfo 类,是第三方包中的类,无法直接在其中添加类注解:
import lombok.Data;@Data
public class UserInfo {private String name;private Integer age;
}
tip:创建该对象的三种方法:
①:调用 set 方法
public class UserInfoComponent {public UserInfo userInfo() {UserInfo userInfo = new UserInfo();userInfo.setName("zhangsan");userInfo.setAge(18);return userInfo;}
}
②:构建构造方法
@Data
public class UserInfo {private String name;private Integer age;public UserInfo(String name, Integer age) {this.name = name;this.age = age;}
}
③:使用 lombok 中的注解生成构造方法(和 ② 一样,只不过使用注解实现)
@AllArgsConstructor // 全参构造函数
@NoArgsConstructor // 无参构造函数
@Data
public class UserInfo {private String name;private Integer age;
}
如下方法构造了该对象:
import com.example.springioc.model.UserInfo;public class UserInfoComponent {public UserInfo userInfo() {return new UserInfo("zhangsan", 18);}
}
想要将该方法创建的对象交给 Spring 管理,只需给该方法添加 @Bean 注解即可
public class UserInfoComponent {@Beanpublic UserInfo userInfo() {return new UserInfo("zhangsan", 18);}
}
测试能否获取该对象:
发现报错了,没有找到该对象
3.3.1 方法注解要配合类注解使用
这是因为 @Bean 需要配合五个类注解使用,任意一个均可:
@Component
public class UserInfoComponent {@Beanpublic UserInfo userInfo() {return new UserInfo("zhangsan", 18);}
}
加上类注解时候,Spring 才知道它需要帮我们管理该对象,管理该类中的 @Bean 方法
再次运行:
3.3.2 定义多个对象
@Component
public class UserInfoComponent {@Beanpublic UserInfo userInfo() {return new UserInfo("zhangsan", 18);}@Beanpublic UserInfo userInfo1() {return new UserInfo("lisi", 20);}
}
运行:
错误提示为:期望有一个对象,结果找到两个
这是因为,我们是根据类型来找的对象,我们可以改为根据名称(该名称与方法名一致)来找:
tip:也可以根据 名称 + 类型 的方式来获取
3.3.3 重命名 Bean
可以通过设置 name 属性给 Bean 对象进行重命名操作,如下:
查看 @Bean 源码:
可以看到名称存放于一个数组中,因此可以给 Bean 命名多个名称:
@Bean(name = {"zhangsan", "userInfo"})public UserInfo userInfo() {return new UserInfo("zhangsan", 18);}@Bean("lisi")public UserInfo userInfo1() {return new UserInfo("lisi", 20);}
当只重命名一个名称时,name= 可以省略,如上 userInfo1
3.4 扫描路径
使用 @Controller、@Service、@Repository、@Component、@Configuration 声明的 bean 生效的前提条件是:能够被 Spring 扫描到,如下例:
再次运行前面已经成功的代码
错误信息是找不到名称为 'zhangsan' 的对象
这是就是因为 Spring 没有扫描到,需要配置扫描路径,让 Spring 扫描到这些注解
可以通过 @ComponnetScan 来配置路径,如下:
这种做法仅了解,不推荐使用
tip:为什么前面没有配置 @ComponentScan 注解也可以呢?
@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在启动类声明注解 @SpringBootApplication 中了
默认扫描的范围是 SpringBoot 启动类所在的包及其子包
推荐做法:把启动类放在我们希望扫描的包的路径下,这样我们定义的 bean 都能被扫描到
4. DI 详解
DI 即依赖注入(Dependency Injection),依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象(简单来说就是把对象取出来放到某个类的属性中)
在上面示例中,使用了 @Autowired 这个注解,完成了依赖注入的操作
关于依赖注入,Spring 给我们提供了三种方式:
- 属性注入(FieldInjection)
- 构造方法注入(ConstructorInjection)
- Setter 注入(SetterInjection)
4.1 属性注入
使用 @Autowired 将 Service 类注入到 Controller 类中
Service 类实现代码如下:
import org.springframework.stereotype.Service;@Service
public class UserService {public void say() {System.out.println("UserService say...");}
}
Controller 类的实现代码如下:
import com.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller // 该注解帮我们 new 的对象就放在容器中(ApplicationContext)
public class UserController {@Autowiredprivate UserService userService;public void say() {System.out.println("UserController say...");userService.say();}
}
获取 Controller 中的 say() 方法:
@SpringBootApplication
public class SpringIoCApplication {public static void main(String[] args) {// 获取 Spring 上下文对象ApplicationContext context = SpringApplication.run(SpringIoCApplication.class, args); // ApplicationContext可以理解为 Spring 的容器// 从 Spring 上下文中获取对象UserController bean = context.getBean(UserController.class);// 使用对象bean.say();}
}
运行结果:
若是去掉 @Autowired,再运行:
4.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,如下:
若是类中有多个构造方法时:
这是因为我们将该对象交给 Spring 管理时,它会 new 一个对象,但是有多个构造方法时,它默认调用无参的构造方法,通过打印展示:
此时需要在希望 Spring 调用的构造方法前面加上 @Autowired 来指定
tip:上面使用的是有参数的构造方法,但是我们并没有给它传入参数,参数哪来的?
是因为我们交给 Spring 管理的对象,如果有参数,这个参数可以自己指定,如果未指定,Spring 会根据名称或者类型,从容器中查找对象,并注入进来,如下例:
4.3 Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解,如下:
private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}
也需要加上 @Autowired,否则会报空指针异常
4.4 三种注入优缺点分析
属性注入:
优点:简洁,使用方便
缺点:
- 只能用于 IoC 容器,非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
- 不能注入一个 Final 修饰的属性
构造函数注入(Spring 4.X 推荐,注意此处是 Spring 版本,不是 Spring Boot 版本)
优点:
- 可以注入 Final 修饰的属性
- 注入的对象不会被修改
- 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
- 通用性好,构造方法是 JDK 支持的,所以更换任何框架都适用
缺点:注入多个对象时,代码会比较繁琐
Setter 注入(Spring 3.X 推荐)
优点:方便在类实例之后,重新对该对象进行配置或者注入
缺点:
- 不能注入一个 Final 修饰的属性
- 注入对象可能会被修改,因为 Setter 方法可能会被多次调用,有被修改的风险
4.5 @Autowired 存在问题
同一类型有多个对象,按照名称注入:
若该类型只有一个对象,则直接注入(不管名称是否一样):
若是同一类型有多个对象,并且名称匹配不上,就会造成启动报错:
为解决上述问题,Spring 提供了以下几种解决方案:
- @Primary(Spring 提供)
- @Qualifier(Spring 提供)
- @Resource(JDK提供)
1. 使用 @Primary 注解:
当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现
2. 使用 @Qualifier 注解:
指定当前要注入的 bean 对象,在 @Qualifier 的 value 属性中,指定注入的 bean 名称
@Qualifier 注解不能单独使用,必须配合 @Autowired 使用
当出现下面情况时:
若同时使用了方案一和方案二,则 @Qualifier 优先级更高,如下运行结果:
3. 使用 @Resource 注解:
是按照 bean 的名称进行注入,通过 name 属性指定要注入的 bean 的名称
4.6 常见面试题:@Autowired 和 @Resource 的区别
1. @Autowired 是根据类型匹配,@Resource 是根据名称匹配
两者基本原则都是根据类型匹配,但是 @Autowired 不能指定名称
2. @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解
tip:Autowired 的装配顺序
5. 总结
5.1 Spring、Spring Boot、Spring MVC 的关系及区别
Spring: 简单来说,Spring是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发.
Spring的主要功能:管理对象,以及对象之间的依赖关系,⾯向切⾯编程,数据库事务管理,数据访 问,web框架⽀持等.
但是Spring具备⾼度可开放性,并不强制依赖Spring,开发者可以⾃由选择Spring的部分或者全 部,Spring可以⽆缝继承第三⽅框架,⽐如数据访问框架(Hibernate、JPA),web框架(如Struts、 JSF)
Spring MVC:SpringMVC是Spring的⼀个⼦框架,Spring诞⽣之后,⼤家觉得很好⽤,于是按照MVC 模式设计了⼀个MVC框架(⼀些⽤Spring解耦的组件),主要⽤于开发WEB应⽤和网络接⼝,所以, Spring MVC是⼀个Web框架.
Spring MVC基于Spring进⾏开发的,天⽣的与Spring框架集成.可以让我们更简洁的进⾏Web层 开发,⽀持灵活的URL到⻚⾯控制器的映射,提供了强⼤的约定⼤于配置的契约式编程⽀持,⾮常 容易与其他视图框架集成,如Velocity、FreeMarker等
Spring Boot: Spring Boot是对Spring的⼀个封装,为了简化Spring应⽤的开发⽽出现的,中⼩型 企业,没有成本研究⾃⼰的框架,使⽤SpringBoot可以更加快速的搭建框架,降级开发成本,让开发 ⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现.
Spring Boot 是个脚⼿架,插拔式搭建项⽬,可以快速的集成其他框架进来.
⽐如想使⽤SpringBoot开发Web项⽬,只需要引⼊SpringMVC框架即可,Web开发的⼯作是 SpringMVC完成的,⽽不是SpringBoot,想完成数据访问,只需要引⼊Mybatis框架即可.
Spring Boot只是辅助简化项⽬开发的,让开发变得更加简单,甚⾄不需要额外的web服务器,直接 ⽣成jar包执⾏即可.
最后⼀句话总结:SpringMVC和SpringBoot都属于Spring,SpringMVC是基于Spring的⼀个 MVC框架,⽽SpringBoot是基于Spring的⼀套快速开发整合包.
5.2 常见注解有哪些?分别是什么作用?
Web url 映射:@RequestMapping
参数接收和接口响应:@RequestParam、@RequestBody、@ResponseBody
bean 的存储:@Controller、@Service、@Repository、@Component、@Configuration、@Bean
bean 的获取:@Autowired、@Primary、@Qualifier、@Resource
@RestController:
- 标识一个类为 RESTful Web 服务的控制器,被 Spring 容器自动识别管理。
- 自动将方法返回值转换为 HTTP 响应体,无需再使用
@ResponseBody
注解,可将对象序列化为 JSON 或 XML 格式返回给客户端。 - 支持多种 HTTP 方法,通过在方法上使用特定注解可指定处理的请求路径和方法。
@GetMapping:
- 标注方法处理 HTTP GET 请求,当客户端发送 GET 请求到指定路径时执行该方法。
- 是
@RequestMapping(method = RequestMethod.GET)
的简化形式,简化请求映射,提高开发效率。
@PostMapping:
- 标注方法处理 HTTP POST 请求,当客户端发送 POST 请求到指定路径时执行该方法。
- 通常用于创建新资源的操作,如在用户管理系统中处理用户注册请求。
@PathVariable
- 获取路径中的变量值:当你的 RESTful API 的 URL 中包含动态部分时,可以使用
@PathVariable
来获取这个动态部分的值。例如,假设你有一个 URL/users/{userId}
,其中{userId}
是一个路径变量。使用@PathVariable("userId")
可以将这个路径变量的值提取出来,传递给处理该请求的方法。 - 增强 API 的灵活性:通过使用路径变量,可以创建更加灵活的 API。不同的资源可以通过不同的路径变量来区分,而不需要为每个资源都定义一个独立的 URL。例如,可以使用路径变量来表示不同的用户 ID、订单 ID 等,从而实现对特定资源的访问。
- 简化参数传递:使用
@PathVariable
可以避免在 URL 中使用查询参数来传递变量值,使 URL 更加简洁和直观。同时,也可以减少方法参数的数量,使代码更加清晰易读。
@RequestPart:
- 用于处理多部分请求中的特定部分,可接收包含文件和其他数据的复杂请求,支持多种媒体类型,如接收 JSON 数据或图片文件等。
- 与
@RestController
和 Spring 的请求处理机制配合使用,在 Spring MVC 框架中实现灵活的请求处理,方便文件上传和数据处理集成,可在控制器方法中用多个带此注解的参数接收不同部分数据进行处理
@CookieValue:
- 可从请求中方便地提取特定 Cookie 的值并绑定到方法参数上,简化 Cookie 处理逻辑。
- 用于在 Web 应用中实现个性化功能,如基于 Cookie 进行用户认证和状态跟踪,还能实现跨页面的数据传递。
@RequestHeader:
- 用于从 HTTP 请求中提取特定请求头的值并绑定到方法参数上,可根据不同请求头进行灵活的业务逻辑处理,比如根据
Content-Type
确定如何解析请求体数据。 - 可实现安全验证和授权,如通过获取
Authorization
请求头进行身份验证;还能进行内容协商和响应定制,根据Accept
请求头返回客户端期望的响应格式。
@ComponentScan:
- 自动扫描指定包及其子包,发现并注册如
@Controller
、@Service
等标注的自定义组件,简化组件配置,避免手动在 XML 配置文件中逐个注册的繁琐过程。 - 构建应用的对象图,建立组件之间的依赖关系,实现依赖注入,同时促进松散耦合架构,提高代码可维护性和可测试性。