深入拆解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会管理它的实例。

代码示例

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(); // 调用它的方法
    }
}

解释

  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,它总是会给你同一个实例

什么是单例?

详见:单例模式

代码示例

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 方法。

好处

  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。

代码示例

java 复制代码
@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。

获取并使用

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是如何帮我们找到需要的第三方的包呢?

详见:[[第三方的扫描路径原理]]

使用了哪些设计模式?

  1. \[工厂模式\]

  2. \[代理模式\]

  3. \[单例模式\]

  4. \[原型模式\]

相关推荐
一只叫煤球的猫1 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员
斐波娜娜1 小时前
Maven详解
java·开发语言·maven
Bug退退退1231 小时前
RabbitMQ 高级特性之事务
java·分布式·spring·rabbitmq
程序员秘密基地1 小时前
基于html,css,vue,vscode,idea,,java,springboot,mysql数据库,在线旅游,景点管理系统
java·spring boot·mysql·spring·web3
皮皮林5511 小时前
自从用了CheckStyle插件,代码写的越来越规范了....
java
小码氓1 小时前
Java填充Word模板
java·开发语言·spring·word
会飞的天明1 小时前
Java 导出word 实现饼状图导出--可编辑数据
java·word
Muxiyale1 小时前
使用spring发送邮件,部署ECS服务器
java·服务器·spring
你的人类朋友2 小时前
🫏光速入门cURL
前端·后端·程序员
01传说2 小时前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js