- 在简单了解IoC与DI中我们已经了解了SpringloC的基本操作,接下来我们来详解IoC。
- 在我们提出IoC控制逆转之前,就是将对象的控制权交换给Spring的IOC容器,由IOC容器创建及管理对象: 也是bean的存储器
Bean的存储
什么是Bean
在Spring中,我们把那些由Spring容器管理的对象叫做"Bean"。它们是应用程序的核心构建块。
Spring如何知道哪些类是Bean
Spring通过特殊的注解来识别哪些类是Bean。常见的有:
- 类注解(五大注解)
@Controller
:用于Web层的控制器。@Service
:用于业务逻辑层。@Repository
:用于数据访问层。@Component
:一个通用的组件注解。@Configuration
:用于定义配置类。
- 方法注解
@Bean
:用于在配置类中明确定义Bean。
@Controller
(控制器存储)
作用:告诉Spring,这个类是一个Web请求的处理器,Spring会管理它的实例。
代码示例:
java
@Controller // 贴上"控制器"标签
public class UserController {
public void sayHi() {
System.out.println("Hi, UserController...");
}
}
解释 :我们给 UserController
贴上了 @Controller
标签,Spring就会把它当作一个Bean来管理。
如何获取这个Bean?
我们可以通过Spring的"管家"------ApplicationContext
来获取它。
java
@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(); // 调用它的方法
}
}
解释:
@SpringBootApplication
包含了@Component
等,它会启动Spring并扫描你的项目,找到所有的Bean。SpringApplication.run()
会创建并返回ApplicationContext
,这就是Spring的"管家"。context.getBean(UserController.class)
:我们向"管家"要一个UserController
类型的Bean。- "管家"会把之前它创建并管理的
UserController
实例"送"给我们。
运行结果:
Hi, UserController...
这证明我们成功地从Spring容器中获取并使用了 UserController
Bean。
ApplicationContext vs BeanFactory
ApplicationContext
:
- 翻译过来就是:Spring上下文,可以理解为Spring的"高级管家"。
- 它在启动时就会预先创建并初始化所有单例(默认)的Bean。
- 提供了更多企业级服务,比如国际化、事件发布、AOP等。
- 推荐使用。
BeanFactory
:
- 可以理解为Spring的"基础管家"。
- 它采用懒加载(Lazy Loading),只有当你真正需要某个Bean时,它才会去创建。
- 功能相对简单。
总结 :ApplicationContext
是 BeanFactory
的超集,功能更强大,更适合实际应用开发。
Bean的命名约定
To: [[Bean的命名约定]]
Bean对象的生命周期(默认单例)
默认情况下,Spring管理的Bean都是"单例"的。
这意味着:无论你向Spring容器请求多少次同一个类型的Bean,它总是会给你同一个实例。
什么是单例?
详见:单例模式
代码示例:
java
@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会管理它的实例。
代码示例:
java
@Service // 贴上"服务"标签
public class UserService {
public void sayHi() {
System.out.println("Hi, UserService...");
}
}
获取并使用:
java
@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会管理它的实例。
代码示例:
java
@Repository // 贴上"仓库"标签
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository...");
}
}
获取并使用:
java
@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,这个类是一个普通的组件,需要被管理。
代码示例:
java
@Component // 贴上"通用组件"标签
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent...");
}
}
获取并使用:
java
@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。
代码示例:
java
@Configuration // 贴上"配置"标签
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi, UserConfiguration...");
}
}
获取并使用:
java
@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
方法。
好处:
- 清晰的职责划分:一眼就能看出这个类在应用程序中的作用。
- 增强功能 :Spring会为这些特定注解提供额外的功能(例如,
@Controller
结合@RequestMapping
处理Web请求,@Repository
可能会有异常转换等)。 - 便于扫描: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。它主要用于以下情况:
- 管理第三方库的对象 :你无法在第三方库的类上添加
@Component
注解。 - 需要复杂的创建逻辑:Bean的创建过程需要多步操作或条件判断。
- 定义多个同类型的Bean:需要创建同一个类的多个不同配置的实例。
如何使用 @Bean
?
你需要在 @Configuration
注解的类中定义一个方法,并在方法上添加 @Bean
。这个方法的返回值就是Spring要管理的Bean。
代码示例:
java
@Configuration // 告诉Spring:这是一个配置类
public class BeanConfig {
@Bean // 告诉Spring:这个方法返回的对象是一个Bean,请你管理它
public User user() {
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
解释:
@Configuration
标记BeanConfig
为配置类。@Bean
标记user()
方法,Spring会执行这个方法,并将返回的User
对象注册为一个Bean。
获取并使用:
java
@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 Bean
System.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
注解的类中。
代码示例:
java
@Configuration // 推荐使用@Configuration
public class BeanConfig {
@Bean
public User user() {
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
运行结果:
User(name=zhangsan, age=18)
定义多个对象
问题 :如果我想创建同一个类(比如 User
)的多个不同配置的Bean,怎么办?
答案 :在 @Configuration
类中,你可以定义多个 @Bean
方法,只要它们的方法名不同即可。
代码示例:
java
@Configuration
public class BeanConfig {
@Bean
public User user1() { // 定义第一个User Bean
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() { // 定义第二个User Bean
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
获取并使用:
java
@SpringBootApplication
public class SpringTocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);
// 通过方法名(即Bean名)获取不同的User Bean
User 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的默认名称。
注意:如果通过类型查找会报错
java
User user1 = (User) context.getBean(User.class);
User user2 = (User) context.getBean(User.class);
因为此时有两个相同类型的Bean
,spring不知道要用哪个,自然就报错了
重命名 Bean
问题 :如果我想给 @Bean
定义的Bean起一个自定义的名字,而不是使用方法名,怎么办?
答案 :可以在 @Bean
注解中通过 name
属性或 value
属性来指定Bean的名字。
代码示例:
java
@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;
}
}
获取并使用:
java
@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
包下,而你的启动类也在这个包下。
java
// 启动类在 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需要扫描的包。
代码示例:
java
@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是如何帮我们找到需要的第三方的包呢?
详见:[[第三方的扫描路径原理]]
使用了哪些设计模式?
-
\[工厂模式\]
-
\[代理模式\]
-
\[单例模式\]
-
\[原型模式\]