Spring IoC 与 DI 核心知识点综合测试题

Spring IoC 与 DI 核心知识点综合测试题

3.3.1 方法注解要配合类注解使用

在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:

@Component public class BeanConfig { @Bean public User user(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } } 

再次执行以上代码,运行结果如下:

3.3.2 定义多个对象

对于同一个类,如何定义多个对象呢?比如多数据源的场景,类是同一个,但是配置不同,指向不同的数据源。

我们看下@Bean的使用:

@Component public class BeanConfig { @Bean public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } @Bean public User user2(){ User user = new User(); user.setName("lisi"); user.setAge(19); return user; } } 

定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象呢?

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

运行结果:

报错信息与解决方案

报错信息显示:期望只有一个匹配,结果发现了两个,user1, user2从报错信息中,可以看出来,@Bean 注解的 bean, bean 的名称就是它的方法名

接下来我们根据名称来获取 bean 对象

@SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { //获取Spring上下文对象 ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args); //根据bean名称, 从Spring上下文中获取对象 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) 

可以看到,@Bean 可以针对同一个类,定义多个对象.

3.3.3 重命名 Bean

1. 五大注解(@Component/@Service/@Controller/@Repository/@Configuration

Spring 会根据类名自动生成 bean name,规则分两种:

  • 情况 1:类名前两个字母都是大写 → bean name 直接等于类名本身
    • 例:class HTTPClient {} → bean name = HTTPClient
    • 例:class SQLParser {} → bean name = SQLParser
  • 情况 2:其他情况 → bean name 是类名的小驼峰写法(首字母小写)
    • 例:class UserService {} → bean name = userService
    • 例:class OrderController {} → bean name = orderController

2. @Bean 注解

当你在配置类里用 @Bean 标注一个方法时:

  • bean name 直接等于方法名

例:

@Bean public UserService userService() { return new UserService(); } 

→ bean name = userService

举个完整例子对比

代码写法生成的 bean name规则
class UserService {}userService小驼峰
class HTTPClient {}HTTPClient前两位大写,保持原样
@Bean public OrderDao orderDao() {}orderDao方法名

💡 小提示:

  • 如果你想自定义 bean name,可以直接在注解里写,比如 @Service("myUserService")@Bean(name = "myOrderDao")
  • 这个规则是 Spring 的 BeanNameGenerator 实现的,目的是保证 bean 名称的可读性和唯一性

可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:

@Bean(name = {"u1","user1"}) public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } 

此时我们使用 u1 就可以获取到 User 对象了,如下代码所示:

此时可通过 context.getBean("u1")context.getBean("user1") 获取对象:

@SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args); User u1 = (User) context.getBean("u1"); System.out.println(u1); } } 
3.3.3.2语法简写规则

单个名称时,大括号也可省略

@Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } 

name= 可省略

@Bean({"u1","user1"}) public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } 
3.3.3.3 扫描路径知识点

Q:使用 @Component/@Service 等四个注解声明的 bean,一定会生效吗?A:不一定。原因:bean 想要生效,还需要被 Spring 扫描到,未被扫描的注解不会被注册为 bean。

A:不一定。原因:bean 想要生效,还需要被 Spring 扫描到,未被扫描的注解不会被注册为 bean。

下⾯我们通过修改项⽬⼯程的⽬录结构,来测试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 容器中不存在名为 u1 的 Bean

  • 可能原因:
    1. @Bean(name = "u1") 的配置类没有被 Spring 扫描到。
    2. @Bean 注解没有被正确添加到类注解(如 @Component/@Configuration)的类中。
    3. Bean 名称拼写错误(如 u1 写成 ui 或其他)。

注意:运行的这个类在扫描五大注解的时候只扫描在同一个路径下的文件,如果把他放到的service里面,其他路径的就不会扫描了

@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); } }

Spring Boot 的 @SpringBootApplication 已经包含了 @ComponentScan默认扫描范围是 “启动类所在的包 + 所有子包”,所以大部分场景不用手动加。

但遇到以下情况,就需要手动配置 @ComponentScan

  1. Bean 所在的包不在启动类的包 / 子包下(比如启动类在 com.example.demo,但 Bean 写在 com.example.service,不在子包);
  2. 想精确控制扫描范围(比如只扫描 servicecontroller 包,排除 entity 包);
  3. 想排除某些不想被扫描的类(比如某个类加了 @Service,但暂时不想让它变成 Bean)。
@ComponentScan 的核心用法
1. 基础用法:指定扫描的包路径
// 启动类 // 手动指定扫描 com.example.service 和 com.example.controller 两个包 @ComponentScan({"com.example.service", "com.example.controller"}) @SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args); } } 
  • 语法:@ComponentScan({"包路径1", "包路径2"}),大括号里可以写多个包;
  • 注意:包路径要写完整的包名(比如 com.example.service,不是 service)。
2. 进阶用法:包含 / 排除指定类
// 扫描 com.example 包,但排除 UserService 类(即使它加了 @Service 也不会变成 Bean) @ComponentScan( basePackages = "com.example", excludeFilters = @ComponentScan.Filter( type = FilterType.ASSIGNABLE_TYPE, classes = UserService.class ) ) @SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args); } } 

这种用法适合临时排除某个类,或者只扫描特定类型的注解(比如只扫描 @Controller,不扫描 @Service)。

3.和 @Configuration 的关联

@Configuration 标记的配置类,也需要被 @ComponentScan 扫描到,否则里面的 @Bean 方法不会被执行:

// 配置类放在 com.example.config 包下 @Configuration public class BeanConfig { @Bean public User user() { return new User("zhangsan", 18); } } // 启动类如果不扫描 com.example.config,BeanConfig 不会被识别,user() 也不会执行 @ComponentScan({"com.example.controller", "com.example.config"}) // 必须包含 config 包 @SpringBootApplication public class SpringIocDemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args); User user = context.getBean(User.class); // 能拿到对象,因为扫描到了 BeanConfig } } 
4.关键注意点(避坑)
  1. 不要重复扫描:手动加 @ComponentScan 时,不要和 @SpringBootApplication 的默认扫描范围重复,否则可能导致 Bean 重复创建;
  2. 包路径要写对:如果包路径写错(比如少写一层),Spring 找不到注解类,会报 NoSuchBeanDefinitionException
  3. Spring Boot 不推荐手动加:除非特殊场景,否则尽量把所有 Bean 放在启动类的包 / 子包下,利用默认扫描,减少配置。

{} 里可以配置多个包路径这种做法仅做了解,不做推荐使用

那为什么前面没有配置 @ComponentScan 注解也可以呢?

@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中了

默认扫描的范围是 SpringBoot 启动类所在包及其子包

在配置类上添加 @ComponentScan 注解,该注解默认会扫描该类所在的包下所有的配置类

推荐做法:

把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到

DI 详解

依赖注入(DI)是 IoC 容器在创建 Bean 时,为其提供运行所依赖资源(对象)的过程,也被称为 “对象注入” 或 “属性装配”。

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

  1. 属性注入(Field Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter 注入(Setter Injection)
4.1 属性注入

属性注入通过 @Autowired 注解实现,将 Service 类注入到 Controller 类中。

Service 类实现:

import org.springframework.stereotype.Service; @Service public class UserService { public void sayHi() { System.out.println("Hi,UserService"); } } 

Controller 类实现:

@Controller public class UserController { // 注入方法1:属性注入 @Autowired private UserService userService; public void sayHi(){ System.out.println("hi,UserController..."); userService.sayHi(); } } 

获取并调用 Controller 方法:

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

运行结果:

4.2 构造方法注入

构造方法注入是在类的构造方法中实现注入,代码如下:

@Controller public class UserController2 { //注入方法2:构造方法 private UserService userService; @Autowired public UserController2(UserService userService) { this.userService = userService; } public void sayHi(){ System.out.println("hi,UserController2..."); userService.sayHi(); } } 

注意事项

  • 如果类只有一个构造方法,@Autowired 注解可以省略;
  • 如果类中有多个构造方法,需要添加 @Autowired 来明确指定使用哪个构造方法。

4.3 Setter 注入

Setter 注入通过类的 Setter 方法实现,需在 set 方法上添加 @Autowired 注解,代码如下:

@Controller public class UserController3 { //注入方法3:Setter方法注入 private UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public void sayHi(){ System.out.println("hi,UserController3..."); userService.sayHi(); } } 

练习思考:尝试一下 set 方法如果不加 @Autowired 注解能注入成功吗?

4.4 三种注入优缺点分析

属性注入
  • 优点:简洁,使用方便;
  • 缺点:
    • 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
    • 不能注入一个 Final 修饰的属性
构造函数注入 (Spring 4.X 推荐)
  • 优点:
    • 可以注入 final 修饰的属性
    • 注入的对象不会被修改
    • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法.
    • 通用性好,构造方法是 JDK 支持的,所以更换任何框架,他都是适用的
  • 缺点:
    • 注入多个对象时,代码会比较繁琐
Setter 注入 (Spring 3.X 推荐)
  • 优点:方便在类实例化之后,重新对该对象进行配置或者注入
  • 缺点:
    • 不能注入一个 Final 修饰的属性
    • 注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险.

4.5 @Autowired 存在问题

当同一类型存在多个 bean 时,使用 @Autowired 会存在问题

@Component public class BeanConfig { @Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } @Bean public User user2() { User user = new User(); user.setName("lisi"); user.setAge(19); return user; } } 
@Controller public class UserController { @Autowired private UserService userService; //注入user @Autowired private User user; public void sayHi(){ System.out.println("hi,UserController..."); userService.sayHi(); System.out.println(user); } } 

运行结果:

报错的原因是,非唯一的 Bean 对象。

解决方案

Spring 提供了以下几种解决方案:

  • @Primary
  • @Qualifier
  • @Resource
1. 使用 @Primary 注解

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

@Component public class BeanConfig { @Primary //指定该bean为默认bean的实现 @Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } @Bean public User user2() { User user = new User(); user.setName("lisi"); user.setAge(19); return user; } } 
2. 使用 @Qualifier 注解

指定当前要注入的 bean 对象。在 @Qualifiervalue 属性中,指定注入的 bean 的名称。

  • @Qualifier 注解不能单独使用,必须配合 @Autowired 使用
@Controller public class UserController { @Qualifier("user2") //指定bean名称 @Autowired private User user; public void sayHi(){ System.out.println("hi,UserController..."); System.out.println(user); } } 
3. 使用 @Resource 注解

是按照 bean 的名称进行注入。通过 name 属性指定要注入的 bean 的名称。

@Controller public class UserController { @Resource(name = "user2") private User user; public void sayHi(){ System.out.println("hi,UserController..."); System.out.println(user); } } 

常见面试题:@Autowired@Resource 的区别

  • @Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
  • @Autowired 默认是按照类型注入,而 @Resource 是按照名称注入。相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。

@Autowired 与 @Resource 核心区别

这张图总结了 Spring 中两个依赖注入注解的核心差异,我帮你补充完整并做进一步解读:

1. 来源不同
  • @Autowired:由 Spring 框架 提供(org.springframework.beans.factory.annotation.Autowired),是 Spring 专属的注入注解。
  • @Resource:由 JDK 提供(javax.annotation.Resource,Java EE 标准),不依赖 Spring 框架,可在其他 IoC 容器中使用。
2. 注入规则不同
  • @Autowired:默认 ** 按类型(byType)** 注入。
    • 先根据属性类型在容器中找匹配的 Bean;
    • 若存在多个同类型 Bean,会再按属性名匹配;
    • 需指定名称时,要配合 @Qualifier("beanName") 使用。
  • @Resource:默认 ** 按名称(byName)** 注入。
    • 优先根据 name 属性或属性名匹配 Bean;
    • 若名称匹配不到,才会降级为按类型匹配;
    • 原生支持 name 参数,可直接指定要注入的 Bean 名称,无需额外注解。
3. 功能与参数对比
特性@Autowired@Resource
来源Spring 框架JDK (Java EE)
默认注入方式按类型 (byType)按名称 (byName)
指定名称需配合 @Qualifier直接用 name 属性
支持参数requiredname, type, lookup
兼容性仅 Spring 容器通用,兼容所有支持 Java EE 的容器
4. 典型使用场景

@Resource:适合多实例场景,可直接指定名称获取目标 Bean,避免类型匹配歧义。

// 直接指定注入名为 userService1 的 Bean @Resource(name = "userService1") private UserService userService; 

@Autowired:适合单类型 Bean 场景,代码更简洁,Spring 生态中最常用。

@Autowired private UserService userService; 

💡 补充说明

  • 在 Spring 4.3+ 后,构造方法注入成为官方推荐,@Autowired 可省略,此时 @Resource 的优势主要体现在精确按名匹配上。
  • @Resource 属于 Java EE 规范,在高版本 JDK 中需要手动引入 jakarta.annotation-api 依赖才能使用。
  • 若项目仅使用 Spring 生态,推荐优先用 @Autowired + @Qualifier;若需跨容器兼容,可考虑 @Resource

Read more

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)

区块链|WEB3:时间长河共识算法(Time River Consensus Algorithm)(原命名为时间证明公式算法(TCC)) 本共识算法以「时间长河」为核心设计理念,通过时间节点服务器按固定最小时间间隔打包区块,构建不可篡改的历史数据链,兼顾区块链的金融属性与信用属性,所有优化机制形成完整闭环,无核心逻辑漏洞,具体总结如下: 一、核心机制(闭环无漏洞) 1. 节点准入与初始化:候选时间节点需先完成全链质押,首个时间节点由所有质押节点投票选举产生,彻底杜绝系统指定带来的初始中心化问题,实现去中心化初始化。 2. 时间节点推导与防作弊:下一任时间节点通过共同随机数算法从上一区块推导(输入参数:上一区块哈希、时间戳、固定数据顺序),推导规则公开可验证;时间节点需对数据顺序签名,任一节点发现作弊(篡改签名、操控随机数等),该节点立即失去时间节点资格并扣除全部质押。质押的核心目的是防止节点为持续获取区块打包奖励作弊,作弊损失远大于收益,确保共同随机数推导百分百不可作弊。 3. 节点容错机制:每个时间节点均配置一组合规质押节点构成的左侧顺邻节点队列(队列长度可随全网节点规

By Ne0inhk
Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家

Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 shelf_modular 的鸿蒙化适配指南 - 掌控服务器路由资产、精密模块治理实战、鸿蒙级服务端专家 在鸿蒙跨平台应用执行高级服务端管理与多维 Shelf 路由资产指控(如构建一个支持全场景秒级交互的鸿蒙大型全量后端服务中枢、处理海量 API Route Payloads 的语义认领或是实现一个具备极致指控能力的资产管理后台路由审计中心)时,如果仅仅依赖官方的基础 Shelf 处理器或者是极其繁琐的手动路由映射,极易在处理“由于模块嵌套导致的资产认领偏移”、“高频服务请求下的认领假死”或“由于多语言环境导致的符号解析冲突死结”时陷入研发代码服务端逻辑崩溃死循环。如果你追求的是一种完全对齐现代模块化标准、支持全量高度可定制路由(Modular-driven Backend)且具备极致指控确定性的方案。今天我们要深度解析的 shelf_modular——一个专注于解决“服务端资产标准化认领与模块化解耦”痛点的顶级工具库,正是帮你打造“鸿蒙超

By Ne0inhk
openclaw 对接完飞书群机器人配置踩坑记:消息不回、Gateway 断开问题排查

openclaw 对接完飞书群机器人配置踩坑记:消息不回、Gateway 断开问题排查

前言 用 OpenClaw 配飞书机器人,踩了两个坑:群消息不回、Gateway 总是断开。排查了好一阵子,总算搞定了,记录一下希望能帮到遇到同样问题的朋友。 发现问题 飞书消息不回复 在飞书群里 @ 了机器人,完全没反应。一开始以为是网络不好或者机器人没上线,但状态显示明明是连接着的,这就奇怪了。 Gateway 频繁断开 每次改完配置跑 openclaw gateway restart,或者根本什么都没干,Gateway 说断就断。再想启动就报错,必须跑一遍 openclaw doctor --fix 重新安装才能用。太影响使用了。 查看原因 飞书机器人 ID 搞错了 翻日志看到这么一句: receive events or callbacks through persistent connection only available in

By Ne0inhk
疆鸿智能EtherCAT转DeviceNet,发那科机器人融入倍福的“焊接红娘”

疆鸿智能EtherCAT转DeviceNet,发那科机器人融入倍福的“焊接红娘”

疆鸿智能EtherCAT转DeviceNet,发那科机器人融入倍福的“焊接红娘” 引言 在汽车制造这样高度自动化、节拍紧凑的生产环境中,各类先进的机器人、PLC以及执行机构往往来自不同厂商,采用不同的总线协议。这种异构网络的“沟通”问题,成为了制约产线柔性和稳定性的关键瓶颈。近期,在某汽车制造厂的车门及配件焊接工段优化项目中,我们成功部署了疆鸿智能EtherCAT转DeviceNet协议转换网关(型号:JH-ECT-MDVN),实现了以倍福(Beckhoff)PLC为主站,通过EtherCAT网络,对发那科(FANUC)机器人(DeviceNet从站)进行精准、高效的实时控制。本文将站在一线调试工程师的视角,深入剖析该网关在设备通讯中所扮演的核心角色,并总结其带来的工程价值。  项目背景:当“高速总线”遇上“成熟节点” 该工段原有的控制系统采用倍福TwinCAT PLC作为主控大脑,其优势在于EtherCAT通讯的高速性与同步性,非常适合多轴联动和快速逻辑处理。然而,工段内的多台发那科焊接机器人,其标准配置的通讯接口为DeviceNet。作为一款成熟且稳定的现场总线,Devic

By Ne0inhk