


专栏:JavaEE 进阶跃迁营
个人主页:手握风云
目录
[一、方法注解 @Bean](#一、方法注解 @Bean)
[1.1. 需要配合类注解使用](#1.1. 需要配合类注解使用)
[1.2. 定义多个对象](#1.2. 定义多个对象)
[1.3. 重命名 Bean](#1.3. 重命名 Bean)
[三、DI 详解](#三、DI 详解)
[3.1. 属性注入](#3.1. 属性注入)
[3.2. 构造方法注入](#3.2. 构造方法注入)
[3.3. Setter 注入](#3.3. Setter 注入)
一、方法注解 @Bean
类注解需要添加到类上,但是有些外部包里面的类,这些文件是只读的,还有一个类可以 new 多个对象。这是我们就可以使用方法注解 @Bean。
1.1. 需要配合类注解使用
java
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;
// 表示这个方法返回的实例将被Spring管理
@Bean
public UserInfo userInfo() {
return new UserInfo(18, "Jack");
}
}
java
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"。

我们只需加上五大类注解就可以解决这个问题。

1.2. 定义多个对象
如果说在多数据源的场景下,类是同一个,但是配置不同,会指向不同的数据源
java
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");
}
}
java
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 = context.getBean(UserInfo.class);
System.out.println(bean1);
}
}

报错信息中显示,期望只有一个匹配,结果发现了两个对象 userInfo1 和 userInfo2,从报错信息中可以看出,通过 @Bean 定义的对象, Bean Name 就是方法名。
java
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);
}
}

1.3. 重命名 Bean
可以通过设置 name 属性给 Bean 对象进行重命名操作。我们先来看下类注解的重命名。
java
package com.yang.test1_27_1;
import org.springframework.stereotype.Controller;
@Controller("userController1")
public class UserController {
public void hello() {
System.out.println("hello Controller...");
}
}
java
package com.yang.test1_27_1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1271Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1271Application.class, args);
UserController bean = (UserController) context.getBean("userController1");
bean.hello();
}
}
方法注解的重命名:
java
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");
}
}
java
package com.yang.test1_27_1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1271Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1271Application.class, args);
UserInfo bean1 = (UserInfo) context.getBean("userInfoOne");
System.out.println(bean1);
UserInfo bean2 = (UserInfo) context.getBean("userInfoTwo");
System.out.println(bean2);
}
}
@Bean 注解的属性值,可以起多个别名。默认情况下,Bean 的名字是方法名,但是,一旦显式指定了 value 或 name 属性,方法名将不再是 Bean 的名字,Bean 的名字将严格由数组中的元素决定。这里的别名集合实际上是一个字符串数组,在 Spring 中,如果数组里面只有一个元素,"{}" 可以省略不写。
java
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");
}
}
java
package com.yang.test1_28_1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1281Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1281Application.class, args);
UserInfo bean1 = (UserInfo) context.getBean("u1");
System.out.println(bean1);
UserInfo bean2 = (UserInfo) context.getBean("u1");
System.out.println(bean2);
}
}
二、扫描路径
SpringBoot 有个特点:约定大于配置。每个 Spring FrameWork 的启动类的路径非常关键,这个决定了那个路径对应下的对象有效。使用五大类注解声明的 Bean,并非一定能在 Spring 容器中生效,关键前提是这些 Bean 所在的类必须被 Spring 扫描到。若未被扫描,即使添加了类注解,Spring 也无法识别并管理该 Bean,会出现 "找不到 Bean" 的异常。
扫描路径的配置方式,使用 @ComponentScan 注解。
java
@ComponentScan({"com.example.demo.component", "com.example.demo.service"}) // 扫描多个包
@SpringBootApplication
public class SpringIocDemoApplication { ... }
日常开发中,多数场景无需显式添加 @ComponentScan,核心原因是 @SpringBootApplication 的源码包含 @ComponentScan ,默认扫描范围是 SpringBoot 启动类所在的包及其所有子包。
三、DI 详解
依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。
关于依赖注入, Spring 提供了3种方式:
- 属性注入(Field Injection)
- 构造方法注入(Constructor Injection)
- Setter 注入(Setter Injection)
3.1. 属性注入
属性注入是 Spring 依赖注入(DI)的三种核心方式之一,其本质是通过 @Autowired 注解,将 IoC 容器中已管理的 Bean(对象)直接注入到目标类的成员属性中,实现目标类对依赖对象的使用,无需手动创建依赖对象实例。比如下面的代码将 UserService 类注入 UserRepository。在 UserRepository 类的私有成员属性上添加 @Autowired 注解,Spring 会自动从容器中找到对应类型的 Bean 并注入。
java
package com.yang.test1_28_2.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void hello() {
System.out.println("hello, Service...");
}
}
java
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...");
}
}
java
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 方法,实现快速注入。
缺点:
- 依赖 IoC 容器 :属性注入仅在 Spring IoC 容器环境中生效,若脱离容器(如手动
new目标类),依赖对象无法注入,会出现 NPE。 - 无法注入 final 属性 :如上文所述,
final属性需初始化时赋值,与属性注入的执行时机冲突。 - 潜在的注入失败风险:注入逻辑隐藏在注解中,若依赖 Bean 不存在或类型不匹配,仅在运行时才会暴露异常(编译期无法感知)。
3.2. 构造方法注入
构造方法注入是 Spring 依赖注入(DI)的核心方式之一,其核心逻辑是:通过目标类的构造方法,将 IoC 容器中已管理的依赖 Bean 传入目标类。IoC 容器在创建目标类实例时,会自动解析构造方法的参数类型,从容器中找到匹配的依赖 Bean 并完成注入,确保目标类在初始化阶段就拥有所需的依赖资源。
如果只有一个构造方法,@Autowired 注解可以省略掉,因为Spring 容器也会自动通过该构造方法注入匹配的依赖 Bean;若目标类存在多个构造方法,必须在需要用于注入的构造方法上添加 @Autowired 注解,明确指定容器使用的构造方法,否则会报错。
java
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...");
}
}
java
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 属性的问题)。示例:java@Controller public class UserController2 { private final UserService userService; // final 修饰的依赖 // 构造方法注入 final 属性 public UserController2(UserService userService) { this.userService = userService; } } -
注入的依赖对象不可修改依赖对象通过构造方法赋值后,若未提供 Setter 方法,外部无法修改该依赖对象,避免了依赖被意外篡改的风险,保证了对象的稳定性。
-
依赖对象初始化完全构造方法是类实例化的核心逻辑,依赖注入在构造方法中执行,意味着目标类在创建完成后,所有依赖已完全初始化,不会出现 "依赖未就绪就被使用" 的情况(避免了属性注入可能出现的运行时空指针风险)。
-
通用性强构造方法是 JDK 原生支持的语法,不依赖 Spring 框架特性,即使更换其他 IoC 框架,构造方法注入的逻辑依然可用,兼容性更高。
缺点:
- 注入多个依赖时代码繁琐 若目标类需要依赖多个 Bean(如同时依赖
UserService、UserRepository),构造方法的参数会增多,代码可读性和维护性下降。
java
// 依赖较多时,构造方法参数冗长
public UserController2(UserService userService, UserRepository userRepository, UserConfig userConfig) {
this.userService = userService;
this.userRepository = userRepository;
this.userConfig = userConfig;
}
3.3. Setter 注入
Setter 注入是 Spring 依赖注入(DI)的三种核心方式之一,其核心逻辑是:通过目标类的 Setter 方法(属性的赋值方法)配合 @Autowired 注解,让 IoC 容器在创建目标类实例后,主动调用 Setter 方法将容器中的依赖 Bean 注入到目标类中。
java
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...");
}
}
java
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();
}
}
优点:
-
支持实例化后重新配置依赖Setter 注入的核心优势是 "灵活性":目标对象创建后,可通过再次调用 Setter 方法修改依赖对象(例如切换数据源、更新配置类等),适合依赖需要动态调整的场景。
java// 对象创建后,可重新注入新的 UserService 实例 UserController3 controller3 = context.getBean(UserController3.class); controller3.setUserService(new UserService() { // 新的实现 @Override public void sayHi() { System.out.println("Hi, 新的 UserService 实现"); } }); controller3.sayHi(); // 输出:Hi, 新的 UserService 实现 -
代码可读性较强Setter 方法明确对应某个属性的赋值逻辑,通过方法名可直接关联依赖对象,便于理解 "哪个依赖通过哪个方法注入"。
缺点:
-
无法注入 final修饰的属性 final 修饰的属性需在对象初始化阶段(构造方法执行时)完成赋值,而 Setter 注入在对象创建后执行,此时
final属性已无法修改,因此 Setter 注入不支持 final 依赖。 -
注入对象存在被篡改的风险由于 Setter 方法可被外部多次调用,若代码中存在误操作(如重复调用 Setter 方法覆盖依赖),可能导致依赖对象被意外修改,破坏对象的稳定性。
-
依赖对象可能未就绪 Setter 注入在对象创建后执行,若目标类在依赖注入前就调用了依赖对象的方法(如在构造方法中使用
userService),会因依赖未注入而抛出NullPointerException,需额外注意代码执行顺序。 -
通用性较弱 Setter 注入依赖 Spring 的
@Autowired注解,是 Spring 框架特有的注入方式,若脱离 Spring 容器(如手动new目标类),需手动调用 Setter 方法赋值,兼容性不如构造方法注入(构造方法是 JDK 原生支持)。