跳到主要内容
Spring IoC 与依赖注入(DI)核心实战解析 | 极客日志
Java java
Spring IoC 与依赖注入(DI)核心实战解析 深入解析 Spring IoC 容器与依赖注入(DI)的核心机制。涵盖 Bean 的定义与命名规则、多 Bean 冲突处理方案(@Primary/@Qualifier)、组件扫描路径配置策略,以及三种依赖注入方式的对比。重点阐述了@Autowired 与@Resource 的区别及最佳实践,帮助开发者构建更稳健的 Spring 应用架构。
菩提 发布于 2026/3/30 更新于 2026/4/25 1 浏览Spring IoC 与 DI 基础
在 Spring 框架的设计中,IoC(控制反转)和 DI(依赖注入)是核心基石。理解 Bean 的注册、命名规则以及扫描机制,是掌握 Spring 的关键。
Bean 注解的使用规范
方法注解 @Bean 必须配合类注解(如 @Component 或 @Configuration)使用,才能将对象正常存储到 Spring 容器中。
@Component
public class BeanConfig {
@Bean
public User user () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(18 );
return user;
}
}
定义多个同类型对象
同一个类如何定义多个对象?比如多数据源场景,类相同但配置不同。我们可以在配置类中定义多个 @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( );
user.setAge( );
user;
}
}
"lisi"
19
return
如果在主程序中直接通过类型获取 Bean,Spring 会报错,提示期望只有一个匹配,结果发现了两个。
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main (String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
User user = context.getBean(User.class);
}
}
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
User user1 = (User) context.getBean("user1" );
User user2 = (User) context.getBean("user2" );
System.out.println(user1);
System.out.println(user2);
可以看到,@Bean 可以针对同一个类定义多个对象,默认 Bean 名称就是方法名。
Bean 名称生成规则 Spring 会根据类名自动生成 Bean Name,规则分两种情况:
类名前两个字母都是大写 :Bean Name 直接等于类名本身。
例:class HTTPClient {} → Bean Name = HTTPClient
例:class SQLParser {} → Bean Name = SQLParser
其他情况 :Bean Name 是类名的小驼峰写法 (首字母小写)。
例:class UserService {} → Bean Name = userService
例:class OrderController {} → Bean Name = orderController
当你在配置类里用 @Bean 标注一个方法时,Bean Name 直接等于方法名 。
代码写法 生成的 Bean Name 规则 class UserService {}userService小驼峰 class HTTPClient {}HTTPClient前两位大写,保持原样 @Bean public OrderDao orderDao() {}orderDao方法名
如果想自定义 Bean Name,可以直接在注解里指定,例如 @Service("myUserService") 或 @Bean(name = "myOrderDao")。
@Bean(name = {"u1", "user1"})
public User user1 () {
User user = new User ();
user.setName("zhangsan" );
user.setAge(18 );
return user;
}
此时可通过 context.getBean("u1") 或 context.getBean("user1") 获取对象。语法上,单个名称时大括号可省略,name= 也可省略:
@Bean("u1")
public User user1 () { ... }
扫描路径配置 使用 @Component/@Service 等注解声明的 Bean,并不一定会生效。Bean 想要生效,还需要被 Spring 扫描到。未被扫描的注解不会被注册为 Bean。
Spring Boot 的 @SpringBootApplication 已经包含了 @ComponentScan,默认扫描范围是'启动类所在的包 + 所有子包' 。所以大部分场景不用手动加。
但在以下情况,需要手动配置 @ComponentScan:
Bean 所在的包不在启动类的包/子包下 。
想精确控制扫描范围 (比如只扫描 service 和 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);
}
}
注意:包路径要写完整的包名(比如 com.example.service),不是相对路径。
进阶用法:包含 / 排除指定类
@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);
}
}
这种用法适合临时排除某个类,或者只扫描特定类型的注解。
关键注意点
不要重复扫描 :手动加 @ComponentScan 时,不要和 @SpringBootApplication 的默认扫描范围重复,否则可能导致 Bean 重复创建。
包路径要写对 :如果包路径写错,Spring 找不到注解类,会报 NoSuchBeanDefinitionException。
推荐做法 :把启动类放在我们希望扫描的包的路径下,这样定义的 Bean 就都可以被扫描到。
依赖注入(DI)详解 依赖注入是 IoC 容器在创建 Bean 时,为其提供运行所依赖资源的过程。Spring 提供了三种主要方式:属性注入、构造方法注入、Setter 注入。
1. 属性注入(Field Injection) 通过 @Autowired 注解实现,将 Service 类注入到 Controller 类中。
@Service
public class UserService {
public void sayHi () {
System.out.println("Hi, UserService" );
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi () {
System.out.println("hi, UserController..." );
userService.sayHi();
}
}
2. 构造方法注入(Constructor Injection) 在类的构造方法中实现注入。这是 Spring 4.X 后官方推荐的方式。
@Controller
public class UserController2 {
private final UserService userService;
@Autowired
public UserController2 (UserService userService) {
this .userService = userService;
}
public void sayHi () {
System.out.println("hi, UserController2..." );
userService.sayHi();
}
}
如果类只有一个构造方法,@Autowired 注解可以省略。
如果类中有多个构造方法,需要添加 @Autowired 来明确指定使用哪个构造方法。
3. Setter 注入(Setter Injection) 通过类的 Setter 方法实现,需在 set 方法上添加 @Autowired 注解。
@Controller
public class UserController3 {
private UserService userService;
@Autowired
public void setUserService (UserService userService) {
this .userService = userService;
}
public void sayHi () {
System.out.println("hi, UserController3..." );
userService.sayHi();
}
}
三种注入优缺点分析
属性注入
优点:简洁,使用方便。
缺点:只能用于 IoC 容器,非 IoC 容器不可用;只有在使用的时候才会出现 NPE(空指针异常);不能注入 Final 修饰的属性。
构造函数注入
优点:可以注入 Final 修饰的属性;依赖对象在使用前一定会被完全初始化;通用性好,JDK 支持,更换框架适用性强。
缺点:注入多个对象时,代码会比较繁琐。
Setter 注入
优点:方便在类实例化之后,重新对该对象进行配置或者注入。
缺点:不能注入 Final 修饰的属性;注入对象可能会被改变(Setter 方法可能被多次调用)。
解决多 Bean 冲突 当同一类型存在多个 Bean 时,使用 @Autowired 会报错。Spring 提供了以下几种解决方案:
1. 使用 @Primary 注解 当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现。
@Component
public class BeanConfig {
@Primary
@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 对象。在 @Qualifier 的 value 属性中,指定注入的 Bean 的名称。
注意:@Qualifier 注解不能单独使用,必须配合 @Autowired 使用。
@Controller
public class UserController {
@Qualifier("user2")
@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 框架 提供(org.springframework.beans.factory.annotation.Autowired),是 Spring 专属的注入注解。
@Resource:由 JDK 提供(javax.annotation.Resource,Java EE 标准),不依赖 Spring 框架,可在其他 IoC 容器中使用。
注入规则不同
@Autowired:默认 按类型(byType) 注入。
先根据属性类型在容器中找匹配的 Bean;
若存在多个同类型 Bean,会再按属性名匹配;
需指定名称时,要配合 @Qualifier("beanName") 使用。
@Resource:默认 按名称(byName) 注入。
优先根据 name 属性或属性名匹配 Bean;
若名称匹配不到,才会降级为按类型匹配;
原生支持 name 参数,可直接指定要注入的 Bean 名称,无需额外注解。
功能与参数对比
特性 @Autowired @Resource 来源 Spring 框架 JDK (Java EE) 默认注入方式 按类型 (byType) 按名称 (byName) 指定名称 需配合 @Qualifier 直接用 name 属性 支持参数 仅 required name, type, lookup 等兼容性 仅 Spring 容器 通用,兼容所有支持 Java EE 的容器
@Resource :适合多实例场景,可直接指定名称获取目标 Bean,避免类型匹配歧义。
@Autowired :适合单类型 Bean 场景,代码更简洁,Spring 生态中最常用。
在 Spring 4.3+ 后,构造方法注入成为官方推荐,@Autowired 可省略,此时 @Resource 的优势主要体现在精确按名匹配 上。
@Resource 属于 Java EE 规范,在高版本 JDK 中需要手动引入 jakarta.annotation-api 依赖才能使用。
若项目仅使用 Spring 生态,推荐优先用 @Autowired + @Qualifier;若需跨容器兼容,可考虑 @Resource。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online