Spring IoC 与 DI 详解:@Bean 注解、扫描路径及依赖注入方式
Spring IoC 容器中的 @Bean 注解用于定义外部对象实例,配合扫描路径配置使 Bean 生效。依赖注入提供属性、构造方法和 Setter 三种方式,各自在代码侵入性、final 支持及灵活性上各有优劣。构造方法注入推荐用于确保依赖不可变且初始化完成,属性注入简洁但需容器环境,Setter 注入支持动态替换依赖。掌握这些机制有助于构建松耦合的 Spring 应用。

Spring IoC 容器中的 @Bean 注解用于定义外部对象实例,配合扫描路径配置使 Bean 生效。依赖注入提供属性、构造方法和 Setter 三种方式,各自在代码侵入性、final 支持及灵活性上各有优劣。构造方法注入推荐用于确保依赖不可变且初始化完成,属性注入简洁但需容器环境,Setter 注入支持动态替换依赖。掌握这些机制有助于构建松耦合的 Spring 应用。


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online


类注解需要添加到类上,但是有些外部包里面的类是只读的,或者一个类需要 new 多个对象。这时可以使用方法注解 @Bean。
package com.yang.test1_26_1.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private int age;
private String name;
}
package com.yang.test1_26_1.model;
import org.springframework.context.annotation.Bean;
import com.yang.test1_26_1.model.UserInfo;
public class Config {
// 表示这个方法返回的实例将被 Spring 管理
@Bean
public UserInfo userInfo() {
return new UserInfo(18, "Jack");
}
}
package com.yang.test1_26_1;
import com.yang.test1_26_1.model.UserInfo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1261Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1261Application.class, args);
UserInfo bean = context.getBean(UserInfo.class);
System.out.println(bean);
}
}
运行会出现 NoSuchBeanDefinitionException。只需加上对应的组件注解就可以解决这个问题。
在多数据源场景下,如果类相同但配置不同,会指向不同的数据源。
package com.yang.test1_26_2;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Controller
public class UserInfo {
private int age;
private String name;
@Bean
public UserInfo userInfo1() {
return new UserInfo(20, "Crane");
}
@Bean
public UserInfo userInfo2() {
return new UserInfo(18, "Larry");
}
}
package com.yang.test1_26_2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1262Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1262Application.class, args);
UserInfo bean1 = (UserInfo) context.getBean("userInfo1");
System.out.println(bean1);
UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
System.out.println(bean2);
}
}
报错信息显示期望只有一个匹配,结果发现了两个对象 userInfo1 和 userInfo2。通过 @Bean 定义的对象,Bean Name 默认就是方法名。
可以通过设置 name 属性给 Bean 对象进行重命名操作。 类注解的重命名:
package com.yang.test1_27_1;
import org.springframework.stereotype.Controller;
@Controller("userController1")
public class UserController {
public void hello() {
System.out.println("hello Controller...");
}
}
方法注解的重命名:
package com.yang.test1_27_1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Controller
public class UserInfo {
private int age;
private String name;
@Bean("userInfoOne")
public UserInfo userInfo1() {
return new UserInfo(20, "Crane");
}
@Bean("userInfoTwo")
public UserInfo userInfoT2() {
return new UserInfo(18, "Larry");
}
}
@Bean 注解的属性值,可以起多个别名。默认情况下,Bean 的名字是方法名,但是,一旦显式指定了 value 或 name 属性,方法名将不再是 Bean 的名字,Bean 的名字将严格由数组中的元素决定。这里的别名集合实际上是一个字符串数组,在 Spring 中,如果数组里面只有一个元素,"{}" 可以省略不写。
package com.yang.test1_28_1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Controller
public class UserInfo {
private int age;
private String name;
@Bean({"userInfoOne", "u1"})
public UserInfo userInfo1() {
return new UserInfo(20, "Crane");
}
@Bean({"userInfoTwo", "u2"})
public UserInfo userInfoT2() {
return new UserInfo(18, "Larry");
}
}
SpringBoot 有个特点:约定大于配置。每个 Spring Framework 的启动类的路径非常关键,这个决定了哪个路径对应下的对象有效。使用五大类注解声明的 Bean,并非一定能在 Spring 容器中生效,关键前提是这些 Bean 所在的类必须被 Spring 扫描到。若未被扫描,即使添加了类注解,Spring 也无法识别并管理该 Bean,会出现'找不到 Bean'的异常。 扫描路径的配置方式,使用 @ComponentScan 注解。
@ComponentScan({"com.example.demo.component", "com.example.demo.service"}) // 扫描多个包
@SpringBootApplication
public class SpringIocDemoApplication { ... }
日常开发中,多数场景无需显式添加 @ComponentScan,核心原因是 @SpringBootApplication 的源码包含 @ComponentScan,默认扫描范围是 SpringBoot 启动类所在的包及其所有子包。
依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象。 关于依赖注入,Spring 提供了 3 种方式:
属性注入是 Spring 依赖注入(DI)的三种核心方式之一,其本质是通过 @Autowired 注解,将 IoC 容器中已管理的 Bean(对象)直接注入到目标类的成员属性中,实现目标类对依赖对象的使用,无需手动创建依赖对象实例。比如下面的代码将 UserService 类注入 UserRepository。在 UserRepository 类的私有成员属性上添加 @Autowired 注解,Spring 会自动从容器中找到对应类型的 Bean 并注入。
package com.yang.test1_28_2.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void hello() {
System.out.println("hello, Service...");
}
}
package com.yang.test1_28_2.repo;
import com.yang.test1_28_2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
@Autowired
private UserService userService;
public void hello() {
userService.hello();
System.out.println("hello, Repository...");
}
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.repo.UserRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1282Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
UserRepository bean = context.getBean(UserRepository.class);
bean.hello();
}
}
通过 Spring 上下文获取 UserRepository 对象并调用方法,可观察到注入成功。 如果去掉 @Autowired 的注解,Spring 不会注入依赖对象,调用方法时就会抛出空指针异常 NullPointerException。 优点:
@Autowired 注解,无需编写构造方法或 Setter 方法,实现快速注入。
缺点:new 目标类),依赖对象无法注入,会出现 NPE。final 属性需初始化时赋值,与属性注入的执行时机冲突。构造方法注入是 Spring 依赖注入(DI)的核心方式之一,其核心逻辑是:通过目标类的构造方法,将 IoC 容器中已管理的依赖 Bean 传入目标类。IoC 容器在创建目标类实例时,会自动解析构造方法的参数类型,从容器中找到匹配的依赖 Bean 并完成注入,确保目标类在初始化阶段就拥有所需的依赖资源。 如果只有一个构造方法,@Autowired 注解可以省略掉,因为 Spring 容器也会自动通过该构造方法注入匹配的依赖 Bean;若目标类存在多个构造方法,必须在需要用于注入的构造方法上添加 @Autowired 注解,明确指定容器使用的构造方法,否则会报错。
package com.yang.test1_28_2.controller;
import com.yang.test1_28_2.service.UserService;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void hello() {
userService.hello();
System.out.println("hello, Controller...");
}
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1282Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
UserController bean = context.getBean(UserController.class);
bean.hello();
}
}
优点:
支持注入 final 修饰的属性:final 修饰的属性需在对象初始化阶段(构造方法执行时)完成赋值,而构造方法注入恰好在此阶段执行,因此可注入 final 依赖(解决了属性注入无法注入 final 属性的问题)。示例:
@Controller
public class UserController2 {
private final UserService userService; // final 修饰的依赖
// 构造方法注入 final 属性
public UserController2(UserService userService) {
this.userService = userService;
}
}
缺点:
UserService、UserRepository),构造方法的参数会增多,代码可读性和维护性下降。Setter 注入是 Spring 依赖注入(DI)的三种核心方式之一,其核心逻辑是:通过目标类的 Setter 方法(属性的赋值方法)配合 @Autowired 注解,让 IoC 容器在创建目标类实例后,主动调用 Setter 方法将容器中的依赖 Bean 注入到目标类中。
package com.yang.test1_28_2.controller;
import com.yang.test1_28_2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void hello() {
userService.hello();
System.out.println("hello, Controller...");
}
}
package com.yang.test1_28_2;
import com.yang.test1_28_2.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1282Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1282Application.class, args);
UserController bean = context.getBean(UserController.class);
bean.hello();
}
}
优点:
final 修饰的属性:final 修饰的属性需在对象初始化阶段(构造方法执行时)完成赋值,而 Setter 注入在对象创建后执行,此时 final 属性已无法修改,因此 Setter 注入不支持 final 依赖。userService),会因依赖未注入而抛出 NullPointerException,需额外注意代码执行顺序。@Autowired 注解,是 Spring 框架特有的注入方式,若脱离 Spring 容器(如手动 new 目标类),需手动调用 Setter 方法赋值,兼容性不如构造方法注入(构造方法是 JDK 原生支持)。