当前位置: 首页 > news >正文

深入拆解Spring核心思想之一:IoC

  • 在简单了解IoC与DI中我们已经了解了SpringloC的基本操作,接下来我们来详解IoC。
  • 在我们提出IoC控制逆转之前,就是将对象的控制权交换给Spring的IOC容器,由IOC容器创建及管理对象: 也是bean的存储器

Bean的存储

什么是Bean

在Spring中,我们把那些由Spring容器管理的对象叫做“Bean”。它们是应用程序的核心构建块。

Spring如何知道哪些类是Bean

Spring通过特殊的注解来识别哪些类是Bean。常见的有:

  1. 类注解(五大注解)
    • @Controller:用于Web层的控制器。
    • @Service:用于业务逻辑层。
    • @Repository:用于数据访问层。
    • @Component:一个通用的组件注解。
    • @Configuration:用于定义配置类。
  2. 方法注解
    • @Bean:用于在配置类中明确定义Bean。

@Controller (控制器存储)

作用:告诉Spring,这个类是一个Web请求的处理器,Spring会管理它的实例。

代码示例

@Controller // 贴上“控制器”标签
public class UserController {public void sayHi() {System.out.println("Hi, UserController...");}
}

解释:我们给 UserController 贴上了 @Controller 标签,Spring就会把它当作一个Bean来管理。

如何获取这个Bean?
我们可以通过Spring的“管家”——ApplicationContext 来获取它。

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {// 启动Spring应用,获取Spring的“管家”ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);// 向“管家”要一个UserController的实例UserController userController = context.getBean(UserController.class);userController.sayHi(); // 调用它的方法}
}

解释

  1. @SpringBootApplication 包含了 @Component 等,它会启动Spring并扫描你的项目,找到所有的Bean。
  2. SpringApplication.run() 会创建并返回 ApplicationContext,这就是Spring的“管家”。
  3. context.getBean(UserController.class):我们向“管家”要一个 UserController 类型的Bean。
  4. “管家”会把之前它创建并管理的 UserController 实例“送”给我们。

运行结果

Hi, UserController...

这证明我们成功地从Spring容器中获取并使用了 UserController Bean。

ApplicationContext vs BeanFactory

ApplicationContext

  • 翻译过来就是:Spring上下文,可以理解为Spring的“高级管家”。
  • 它在启动时就会预先创建并初始化所有单例(默认)的Bean
  • 提供了更多企业级服务,比如国际化、事件发布、AOP等。
  • 推荐使用

BeanFactory

  • 可以理解为Spring的“基础管家”。
  • 它采用懒加载(Lazy Loading),只有当你真正需要某个Bean时,它才会去创建。
  • 功能相对简单。

总结ApplicationContextBeanFactory 的超集,功能更强大,更适合实际应用开发。

Bean的命名约定

To: [[Bean的命名约定]]

Bean对象的生命周期(默认单例)

默认情况下,Spring管理的Bean都是“单例”的。
这意味着:无论你向Spring容器请求多少次同一个类型的Bean,它总是会给你同一个实例

什么是单例?
详见:单例模式

代码示例

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserController userController1 = context.getBean(UserController.class);UserController userController2 = context.getBean(UserController.class);UserController userController3 = context.getBean(UserController.class);// 打印这些对象的内存地址System.out.println(userController1);System.out.println(userController2);System.out.println(userController3);}
}

运行结果

com.example.demo.controller.UserController@95226e402
com.example.demo.controller.UserController@95226e402
com.example.demo.controller.UserController@95226e402

解释:你会发现打印出的内存地址是完全相同的,这证实了Spring默认只创建了一个 UserController 的实例。

@Service (服务存储)

作用:告诉Spring,这个类是一个业务逻辑组件,Spring会管理它的实例。

代码示例

@Service // 贴上“服务”标签
public class UserService {public void sayHi() {System.out.println("Hi, UserService...");}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserService userService = context.getBean(UserService.class);userService.sayHi();}
}

运行结果

Hi, UserService...

解释:和 @Controller 类似,Spring管理 UserService,我们可以从容器中获取并使用它。

@Repository (仓库存储)

作用:告诉Spring,这个类是一个数据访问组件(通常用于数据库操作),Spring会管理它的实例。

代码示例

@Repository // 贴上“仓库”标签
public class UserRepository {public void sayHi() {System.out.println("Hi, UserRepository...");}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserRepository userRepository = context.getBean(UserRepository.class);userRepository.sayHi();}
}

运行结果

Hi, UserRepository...

解释:同理,Spring也管理 UserRepository,并能成功获取。

@Component (组件存储)

作用:一个通用的组件注解,当你不知道一个类具体属于哪一层(Controller、Service、Repository)时,可以使用它。它告诉Spring,这个类是一个普通的组件,需要被管理。

代码示例

@Component // 贴上“通用组件”标签
public class UserComponent {public void sayHi() {System.out.println("Hi, UserComponent...");}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserComponent userComponent = context.getBean(UserComponent.class);userComponent.sayHi();}
}

运行结果

Hi, UserComponent...

解释@Component 同样能让Spring管理你的类。

@Configuration (配置存储)

作用:告诉Spring,这个类是一个配置类。通常,我们会在 @Configuration 类中使用 @Bean 注解来手动定义和配置Bean。

代码示例

@Configuration // 贴上“配置”标签
public class UserConfiguration {public void sayHi() {System.out.println("Hi, UserConfiguration...");}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);userConfiguration.sayHi();}
}

运行结果

Hi, UserConfiguration...

解释:Spring同样会管理 @Configuration 类本身,你也可以像获取其他Bean一样获取它。

总结:这些注解都是告诉Spring“管家”:“我是一个需要你管理的组件,请你帮我创建实例,并在需要的时候提供给我。” 它们是Spring实现依赖注入和控制反转(IoC)的基础。

好的,我们继续用这种简洁的讲解方式来解析您提供的图片内容。

为什么有这么多类注解?

问题:既然 @Component 已经能让Spring管理Bean了,为什么还需要 @Controller@Service@Repository@Configuration 这些注解呢?

答案:它们都是 @Component 的“特化版本”,除了让Spring管理Bean外,还赋予了额外的“语义”和功能。

  • @Controller:表明这个类是Web层的控制器,处理HTTP请求。
  • @Service:表明这个类是业务逻辑层的组件,包含核心业务处理。
  • @Repository:表明这个类是数据访问层的组件,通常用于与数据库交互。
  • @Configuration:表明这个类是一个配置类,通常用于定义 @Bean 方法。

好处

  1. 清晰的职责划分:一眼就能看出这个类在应用程序中的作用。
  2. 增强功能:Spring会为这些特定注解提供额外的功能(例如,@Controller 结合 @RequestMapping 处理Web请求,@Repository 可能会有异常转换等)。
  3. 便于扫描:Spring可以根据这些注解进行更精细的扫描和处理。

它们的关系
这些注解(@Controller@Service@Repository@Configuration)都自带 @Component 的功能。这意味着,只要你使用了它们,你的类就已经是Spring管理的一个Bean了。
源码:
![[JAVA/EE进阶/Spring.excalidraw.md#^group=a8sq7vgv6xOwlFPq3LRZ9|700]]
你可以看到,@Controller@Service@Repository@Configuration 内部都包含了 @Component 注解。

方法注解 @Bean

问题:我们已经有了 @Component 等注解来管理类,为什么还需要 @Bean 注解?

答案
@Bean 用于在 @Configuration 注解的类中,通过方法来明确地定义和配置一个Bean。它主要用于以下情况:

  1. 管理第三方库的对象:你无法在第三方库的类上添加 @Component 注解。
  2. 需要复杂的创建逻辑:Bean的创建过程需要多步操作或条件判断。
  3. 定义多个同类型的Bean:需要创建同一个类的多个不同配置的实例。

如何使用 @Bean
你需要在 @Configuration 注解的类中定义一个方法,并在方法上添加 @Bean。这个方法的返回值就是Spring要管理的Bean。

代码示例

@Configuration // 告诉Spring:这是一个配置类
public class BeanConfig {@Bean // 告诉Spring:这个方法返回的对象是一个Bean,请你管理它public User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}

解释

  1. @Configuration 标记 BeanConfig 为配置类。
  2. @Bean 标记 user() 方法,Spring会执行这个方法,并将返回的 User 对象注册为一个Bean。

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);User user = context.getBean(User.class); // 从Spring获取User BeanSystem.out.println(user);}
}

运行结果

User(name=zhangsan, age=18)

解释:Spring成功地通过 @Bean 方法创建并管理了 User 对象。

方法配合类注解使用

问题@Configuration@Component 都可以让Spring扫描到类,那它们有什么区别?

答案

  • @Configuration更强调配置。它会生成一个代理类,确保 @Bean 方法在被多次调用时,仍然返回同一个单例Bean(如果Bean是单例作用域)。
  • @Component:一个通用组件。如果在一个 @Component 类中定义 @Bean 方法,Spring不会生成代理,每次调用 @Bean 方法可能会创建新的实例(除非你手动确保单例)。

推荐做法定义 @Bean 方法时,总是将它们放在 @Configuration 注解的类中

代码示例

@Configuration // 推荐使用@Configuration
public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}

运行结果

User(name=zhangsan, age=18)

定义多个对象

问题:如果我想创建同一个类(比如 User)的多个不同配置的Bean,怎么办?

答案:在 @Configuration 类中,你可以定义多个 @Bean 方法,只要它们的方法名不同即可。

代码示例

@Configuration
public class BeanConfig {@Beanpublic User user1() { // 定义第一个User BeanUser user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2() { // 定义第二个User BeanUser user = new User();user.setName("lisi");user.setAge(19);return user;}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);// 通过方法名(即Bean名)获取不同的User BeanUser user1 = (User) context.getBean("user1");User user2 = (User) context.getBean("user2");System.out.println(user1);System.out.println(user2);}
}

运行结果

User(name=zhangsan, age=18)
User(name=lisi, age=19)

解释:Spring会使用 @Bean 方法的名字作为Bean的默认名称。

注意:如果通过类型查找会报错

        User user1 = (User) context.getBean(User.class);User user2 = (User) context.getBean(User.class);

因为此时有两个相同类型的Bean,spring不知道要用哪个,自然就报错了

重命名 Bean

问题:如果我想给 @Bean 定义的Bean起一个自定义的名字,而不是使用方法名,怎么办?

答案:可以在 @Bean 注解中通过 name 属性或 value 属性来指定Bean的名字。

代码示例

@Configuration
public class BeanConfig {@Bean(name = "u1") // 给这个Bean起名为“u1”public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Bean("u2") // 也可以直接用value属性public User user2() {User user = new User();user.setName("lisi");user.setAge(19);return user;}
}

获取并使用

@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);User user1 = (User) context.getBean("u1"); // 使用自定义的名字获取System.out.println(user1);}
}

运行结果

User(name=zhangsan, age=18)

解释:现在你可以通过自定义的名字 u1 来获取 User Bean了。

扫描路径

问题:我给类加了 @Component 等注解,Spring就能找到它们吗?

答案:不一定。Spring需要知道去哪里“扫描”这些注解。

默认扫描范围
Spring Boot应用程序默认会从启动类(带有 @SpringBootApplication 的类)所在的包及其子包中扫描Bean。

代码示例
假设你的 User 类在 com.example.demo.controller 包下,而你的启动类也在这个包下。

// 启动类在 com.example.demo.controller 包下
package com.example.demo.controller;@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);User u1 = (User) context.getBean("u1"); // 如果User在扫描路径内,就能找到System.out.println(u1);}
}

如果Bean不在默认扫描路径内
如果你的 User 类在 com.example.demo.model 包下,而启动类在 com.example.demo.controller 包下,那么Spring默认是找不到 User Bean的。

运行结果

// 报错:No bean named 'u1' available

解释:Spring找不到名为 u1 的Bean,因为它没有扫描到 com.example.demo.model 包。

如何解决?
你可以使用 @ComponentScan 注解来明确指定Spring需要扫描的包。

代码示例

@SpringBootApplication
@ComponentScan("com.example.demo") // 告诉Spring:扫描这个包及其子包
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);User u1 = (User) context.getBean("u1");System.out.println(u1);}
}

解释:通过 @ComponentScan("com.example.demo"),Spring会扫描 com.example.demo 包及其所有子包,这样就能找到 User Bean了。

可以指定多个扫描路径
@ComponentScan({"com.example.demo.controller", "com.example.demo.model"})

推荐做法
将你的启动类放在项目的主包下(例如 com.example.demo),这样默认的扫描范围就能覆盖到你所有的业务代码,而无需额外配置 @ComponentScan

在没有加@ComponentScan的情况下,spring是如何帮我们找到需要的第三方的包呢?
详见:[[第三方的扫描路径原理]]

使用了哪些设计模式?

  1. [[工厂模式]]
  2. [[代理模式]]
  3. [[单例模式]]
  4. [[原型模式]]
http://www.dtcms.com/a/272230.html

相关文章:

  • 天津医大用网络药理学+分子对接发表中科院二区IF5
  • 【Python】基于Python提取图片验证码
  • SYM32第二十天 ESP8266-01S和电脑实现串口通信(3)
  • 羊肚菌自动采收车设计cad【7张】+三维图+设计说明书
  • 电脑息屏工具,一键黑屏超方便
  • 双esp8266-01之间UDP透传传输,自定义协议
  • LlamaFactory Demo
  • 使用langchain连接llama.cpp部署的本地deepseek大模型开发简单的LLM应用
  • CTFHub————Web{信息泄露[备份文件下载(vim缓存、.DS_Store)]}
  • turbopack打包机制
  • SQL的初步学习(一)(以MySQL为例)
  • 重置 Rust 工具链​
  • 缺乏项目进度对比历史数据,如何建立进度基线
  • 深度学习_全连接神经网络
  • UE5多人MOBA+GAS 17、制作小兵的动画蓝图、攻击GA以及死亡和复活的AI感知开关
  • Ajax之核心语法详解
  • Vue Vue-route (4)
  • Ubuntu基础(Python虚拟环境和Vue)
  • 堆的应用(讲解超详细)
  • Uniapp中的uni.scss
  • 2025.07.09华为机考真题解析-第一题100分
  • 【VLAs篇】05:RDT模型结构和流程分析
  • HTML颜色定义
  • 深入了解Modbus TCP:工业通信的“通用语言”
  • Docker-构建镜像并实现LNMP架构
  • C语言 | 函数核心机制深度解构:从底层架构到工程化实践
  • 西电考研录取:哪些省份考研上岸西电更容易?
  • PyTorch Tensor 的创建与操作入门
  • 低版本hive(1.2.1)UDF实现清除历史分区数据
  • 1.1.1数据类型与变量——AI教你学Django