首页 > 编程语言 >Spring IoC容器与Bean管理完全解析

Spring IoC容器与Bean管理完全解析

来源:互联网 2026-07-22 08:06:10

SpringIoC容器通过@Controller、@Service、@Repository、@Component、@Configuration五大类注解以及@Bean方法注解来注册和管理Bean对象,默认采用单例模式。依赖注入支持属性注入、构造方法注入和Setter注入三种方式,其中构造方法注入被推荐。当存在多个同类型Bean时,可通过@Primary、@Q

一、Bean的存储

先聊聊Bean的存储。上一节我们提到,要把某个对象交给IoC容器管理,直接在类上添加一个@Component注解就行。但Spring为了适配Web开发,还提供了更细分的注解,让不同层次的代码各司其职。

类注解:@Controller、@Service、@Repository、@Component、@Configuration

长期稳定更新的攒劲资源: >>>点此立即查看<<<

方法注解:@Bean

1.1 @Controller(控制器存储)

用@Controller来存储Bean,操作起来非常直观。

Spring IoC容器与Bean管理完全解析

接下来,从Spring容器中把这个对象取出来看看。

Spring IoC容器与Bean管理完全解析

上面这段代码是根据类型来查找对象的。但问题来了——如果同一个类型在Spring容器中有多个Bean,该怎么办?(关于Bean的命名,上一节已经聊过,这里就不重复了。)

1.根据bean名称获取bean

Object getBean(String var1)throws BeansException;

2.根据bean名称和类型获取bean

T getBean(String var1,Class var2)throws BeansException;

3.根据类型获取bean

T getBean(Class var1)throws BeansException;

4. 按bean名称和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean

Object getBean(String var1,Class var2)throws BeansException;

5.按bean类型和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean

T getBean(String var1,Class var2)throws BeansException;

实际开发中,前三种方法的出镜率最高,后面几个注解也一样,咱们就重点展示这三种。

Spring IoC容器与Bean管理完全解析

检查一下,拿到的这几个对象是不是同一个。

Spring IoC容器与Bean管理完全解析

从地址来看,完全一致,说明获取的是同一个Bean对象。Spring容器默认是单例模式,这点很关键。

1.2 @Service(服务存储)

用@Service来存储Bean,用法和@Controller几乎一样。

Spring IoC容器与Bean管理完全解析

读取Bean也是同样的套路。

Spring IoC容器与Bean管理完全解析

1.3 @Repository(仓库存储)

@Repository,顾名思义,用于数据访问层。

Spring IoC容器与Bean管理完全解析

读取Bean的方式,依然是一脉相承。

Spring IoC容器与Bean管理完全解析

1.4 @Component(组件存储)

@Component是通用组件注解,它像是一个基础款,其他注解都是它的衍生品。

Spring IoC容器与Bean管理完全解析

读取Bean,步骤不变。

Spring IoC容器与Bean管理完全解析

1.5 @Configuration(配置存储)

@Configuration用于处理项目的配置信息,比如数据源配置。

Spring IoC容器与Bean管理完全解析

读取Bean,没毛病。

Spring IoC容器与Bean管理完全解析

1.6 这些注解的用途

  • @Controller:控制层,负责接收请求、处理请求并给出响应。
  • @Service:业务逻辑层,专门处理具体的业务逻辑。
  • @Repository:数据访问层,也叫持久层,负责与数据库打交道。
  • @Configuration:配置层,专门处理项目中的各种配置信息。
  • @Component:通用组件注解,那些不属于上述分层的普通业务类、工具类,就靠它来标记,然后被Spring自动扫描并存入IoC容器。

@Controller@Service@Repository@Configuration 都是@Component的衍生注解。除了@Controller不完全等同于@ResponseBody,其余衍生注解主要做分层语义区分,底层功能其实一致——都是把类交给Spring IoC容器管理。

Spring IoC容器与Bean管理完全解析

打个比方:杯子有喝水的杯子,也有刷牙的杯子。虽然都是杯子,但咱们日常还是倾向于用刷牙的杯子刷牙,用喝水的杯子喝水。这些注解的角色划分,本质上也是为了让代码结构更清晰。

1.7 ApplicationContext VS BeanFactory

  • 从继承关系和功能来看:Spring容器有两个顶级接口:BeanFactory和ApplicationContext。BeanFactory提供最基础的容器访问能力,而ApplicationContext是它的子类,除了继承所有功能,还额外支持国际化、资源访问和事件传播等高级特性。
  • 从性能方面来看:ApplicationContext倾向于一次性加载并初始化所有Bean,而BeanFactory则是按需加载,谁用谁加载,因此更轻量。

二、方法注解@Bean

上面介绍了五大类注解,但实际开发中会遇到两个棘手的问题:

1. 外部包里的类,没法添加类注解

2. 一个类需要多个对象,比如配置多个数据源

这两个场景,就需要请出方法注解@Bean了。注意,@Bean注解必须搭配五大类注解才能使用。

2.1 搭配类注解的使用

Spring IoC容器与Bean管理完全解析

Spring IoC容器与Bean管理完全解析

在Spring的设计中,@Bean方法注解必须配合类注解,才能把对象正常存储到容器中。

如果把@Component注掉试试看?

Spring IoC容器与Bean管理完全解析

2.2 定义多个对象

Spring IoC容器与Bean管理完全解析

2.3 重命名Bean

2.3.1 方法一:最完整的写法

Spring IoC容器与Bean管理完全解析

2.3.2 方法二:省略“name={ }”

Spring IoC容器与Bean管理完全解析

2.3.3 方法三:当只有一个名称时,{} 也可省略

Spring IoC容器与Bean管理完全解析

三、Bean的扫描路径

问题来了:用五大注解声明的Bean,一定会生效吗?

答案是不一定。 原因很简单:Bean想要生效,还得被Spring扫描到才行。

下面,我们通过调整项目工程的目录结构,来测试一下Bean是否生效。

Spring IoC容器与Bean管理完全解析

然后运行下面的代码:

@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        // 获取 Spring 上下文对象
        ApplicationContext context = 
            SpringApplication.run(SpringIocDemoApplication.class, args);
        // 从 Spring 上下文中获取对象
        User u1 = (User) context.getBean("u1");
        // 使用对象
        System.out.println(u1);
    }
}

运行结果:

Spring IoC容器与Bean管理完全解析

报错了: 找不到名称为“u1”的Bean。

为什么找不到?

使用五大注解声明的Bean,要想生效,还得配置扫描路径,让Spring把这些注解扫进来。这个任务就交给@ComponentScan

@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        // 获取 Spring 上下文对象
        ApplicationContext context = 
            SpringApplication.run(SpringIocDemoApplication.class, args);
        // 从 Spring 上下文中获取对象
        User u1 = (User) context.getBean("u1");
        // 使用对象
        System.out.println(u1);
    }
}

{} 里可以配置多个包路径,比如:@ComponentScan({"com.example.demo", "com.example.service"})

注意: 这种做法了解一下即可,并不推荐在生产环境使用。

那为什么前面没有显式配置@ComponentScan也能跑起来呢?因为@ComponentScan其实已经包含在启动类上的@SpringBootApplication注解里了。默认情况下,它会扫描启动类所在包及其所有子包。

推荐做法: 把启动类放在我们希望扫描的根包路径下,这样所有定义的Bean都能被自动扫描到。

扫描路径配置总结

  1. 默认扫描:Spring Boot项目默认扫描启动类所在包及其所有子包。
  2. 自定义扫描:使用@ComponentScan注解可以指定要扫描的包路径。
  3. 多包扫描:通过数组形式指定多个包路径。
  4. 最佳实践:合理组织项目结构,把需要被扫描的类放在启动类的子包中。

合理配置扫描路径,是确保Spring能够正确发现和管理所有Bean组件的基础,也是IoC容器正常工作的前提。

四、依赖注入(DI)详解

上面聊的都是控制反转(IoC)的细节,接下来看看依赖注入(DI)。

依赖注入这个过程, 简单说,就是IoC容器在创建Bean时,顺手把运行时需要的资源(也就是其他对象)给塞进去。

Spring提供了三种依赖注入的方式:

  1. 属性注入
  2. 构造方法注入
  3. Setter注入

4.1 属性注入 @Autowired

代码实现如下:

Spring IoC容器与Bean管理完全解析

运行效果:

Spring IoC容器与Bean管理完全解析

4.2 构造方法注入

代码实现:

Spring IoC容器与Bean管理完全解析

运行效果:

Spring IoC容器与Bean管理完全解析

如果只有一个构造方法,@Autowired可以省略。

如果有多个构造方法,默认采用无参构造方法。

可以通过@Autowired来指定使用哪个构造方法。

4.3 setter注入

代码实现:

Spring IoC容器与Bean管理完全解析

运行效果:

Spring IoC容器与Bean管理完全解析

Setter注入和属性的Setter方法实现类似,区别在于set方法上需要加上@Autowired注解。

4.4 三种注入的优缺点分析

Spring提供了三种依赖注入方式,各有各的适用场景。了解它们之间的差异,能在实际开发中做出更合适的选择。

1. 属性注入(Field Injection)

优点:

  • 简洁直观:代码量最少,直接在字段上加上@Autowired就完事。
  • 使用方便:不用写额外的构造方法或Setter方法。
  • 可读性好:依赖关系一目了然,类依赖什么一眼就能看出来。

缺点:

  • 仅适用于IoC容器:脱离Spring容器,这招就不好使了。
  • 空指针风险:运行时才能发现空指针异常,编译期查不出来。
  • 无法注入final字段:不能注入被final修饰的属性。
  • 测试困难:单元测试时得依赖Spring容器,或者用反射来设置字段。
  • 违反单一职责原则:容易导致类依赖过多,职责混乱。

2. 构造方法注入(Constructor Injection)

优点:

  • 可以注入final字段:支持注入被final修饰的属性,保证依赖不可变。
  • 依赖不可变:注入的对象在构造完成后就不会被修改,状态一致性有保障。
  • 完全初始化:依赖在使用前一定被完全初始化,因为构造方法在类加载时就执行了。
  • 通用性好:构造方法是JDK标准特性,不依赖特定框架,换框架一样能用。
  • 便于测试:在单元测试中直接通过构造方法传入模拟对象就行。
  • 强制依赖:明确类的必需依赖,避免依赖缺失。

缺点:

  • 代码略显繁琐:依赖多了,构造方法的参数列表会变长。
  • 循环依赖问题:存在循环依赖时,构造方法注入会直接报错。

注意事项:如果类只有一个构造方法,@Autowired可以省略;如果有多个构造方法,需要加@Autowired来指定用哪个。

3. Setter 注入(Setter Injection)

优点:

  • 灵活性高:方便在类实例化之后,重新配置或注入对象。
  • 可选依赖:适合非必需依赖,可以设默认值或为空。
  • 便于继承:子类可以重写Setter方法来改变注入行为。
  • 解决循环依赖:某些情况下能解决构造方法注入无法处理的循环依赖。

缺点:

  • 无法注入final字段:不能注入被final修饰的属性。
  • 依赖可能被改变:Setter方法可能被多次调用,存在被修改的风险。
  • 对象状态不稳定:在Setter方法被调用前,依赖可能还没初始化。
  • 时序问题:得确保在对象使用前,所有必要的Setter方法都被调用了。

三种注入方式对比总结

特性属性注入构造方法注入Setter 注入
代码简洁性★★★★★★★★☆☆★★★★☆
不可变性★☆☆☆☆★★★★★★★☆☆☆
测试友好性★★☆☆☆★★★★★★★★★☆
框架通用性★☆☆☆☆★★★★★★★★★☆
循环依赖处理★★★★☆★☆☆☆☆★★★★☆
Spring 官方推荐Spring 4.x 之前Spring 4.x 之后Spring 3.x 推荐

选择建议

  1. 强制依赖:用构造方法注入,确保依赖在对象创建时就正确设置。
  2. 可选依赖:用Setter注入,灵活性更高。
  3. 快速原型:可以用属性注入快速搭建,但生产环境还是建议构造方法注入。
  4. 不可变对象:必须用构造方法注入。
  5. 测试驱动开发:优先考虑构造方法注入,便于编写单元测试。

在实际开发中,Spring官方从4.x版本开始推荐构造方法注入,因为它能保证依赖的不可变性和完全初始化,同时提高代码的可测试性。当然,具体选哪种,还得看项目需求和团队规范。

4.5 @Autowired存在的问题

当同一个类型存在多个Bean时,@Autowired就会出问题。

Spring IoC容器与Bean管理完全解析

Spring提供了三种解决方案:

  • @Primary
  • @Qualifier
  • @Resource

4.5.1 @Primary

@Primary注解:当存在多个相同类型的Bean时,加上@Primary,指定一个默认实现。

Spring IoC容器与Bean管理完全解析

4.5.2 @Qualifier

@Qualifier注解:通过value属性指定要注入的Bean名称。注意,@Qualifier不能单独使用,必须配合@Autowired。

Spring IoC容器与Bean管理完全解析

4.5.3 @Resource

@Resource注解:按Bean的名称进行注入,通过name属性指定要注入的Bean名称。

Spring IoC容器与Bean管理完全解析

五、小结

感觉这几天有点昼夜颠倒,好困好困好困好累好累,怀疑是不是日常没什么运动量导致身体虚虚的。想报个游泳班,但是都好贵啊,最后还是决定不报了,等后面有机会了再报吧。明天吃完火锅后,就开始减肥控糖,一定要瘦瘦瘦。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。