SpringBoot Bean 概念理解和使用
目录
Bean 简单解释
Spring 维护一个容器(IoC),容器内有很多的Bean。如果需要某些Bean,就直接 @Autowired
即可。 Spring 会寻找对应的 Bean 自动装配,如果找不到则抛出异常。
图例: 用 Bean 之前,用 Bean 之后
iPhone 和 Android 手机都有摄像机功能。它们每次构造时都要新建摄像机对象,否则调用的时候会抛出 NullPointer
。

不使用 Bean,构造函数里面要手动初始化
上图可以看到,iPhone 和 Android 同时要初始化摄像机,可见已经有重复的代码了。
改成用 Bean 的方法:

声明 Bean 之后,用 @Autowired 就可以自动赋值
具体的步骤:
- PhoneConfig 类用
@Bean
通知 Spring 新建一个 Camera 对象,放到 Bean 池。 - iPhone类
@Autowired
通知 Spring 抽一个 Camera 对象过来。 - AndroidPhone类 同 iPhone 一样的做法。
- Spring 在 Bean 池找到有 Camera 这个对象,就直接装到有
@Autowired
一方。
引入 Spring Bean 后,new iPhone()
时,Camera 对象就已经被 Spring 装上去了,不用再 new Camera()
了。
声明与装配 Bean
有很多种方法给 Spring 添加 Bean,传统的 Spring App 用 XML 定义:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="myid" class="app.demo.MyBean"></bean>
</beans>
…
XML没有语法检查,更不会管 class 是否合理,所以容易出错,而且 XML 头部还有固定格式,我相信大家都不喜欢记这些东西。这也是 Spring 一直被社区要求改进的一点,xml配置文件维护起来一点也不方便。
我推荐用 annotation,即 @XXXX
的方法来添加 Bean。
用 @Component 和 @Bean 声明
把 @Component
标注在类之前,可以让 Spring 来扫描里面 Bean。
同时,这个类也会被当作 Bean。
@Component
class MyComponent {
@Bean
String ipv4Address () {
return "127.0.0.1";
}
void print() {
System.out.println("[MyComponent] 我不仅导出一个 String 类型的 bean,我自己也是一个 bean");
}
}
// 声明 SpringBootApplication 后,Spring 才知道这个类是程序入口
@SpringBootApplication
class MyApp implements CommandLineRunner{
// 自动装上 String 类型的 Bean
@Autowired
String address;
// 自动装上 MyComponent 对象
@Autowired
MyComponent myComponent;
// 实现 CommandLineRunner 方法,将在 SpringApplication.run 时调用
@Override
public void run(String... args) throws Exception {
// 不需要 this.address = XXXX 一堆东西,直接调用
System.out.println("字符串 bean 自动装配的值: " + this.address);
this.myComponent.print();
}
// SpringApplication.run 才能打开 Spring 的功能
public static void main (String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
运行结果
字符串 bean 自动装配的值: 127.0.0.1
[MyComponent] 我不仅导出一个 String 类型的 bean,我自己也是一个 bean
用 @Configuration 和 @Service 声明
根据源代码,@Configuration
、 @Service
、 @Component
有同样的效果。
@Component, @Configuration, @Service 区别
通常情况下,是为了规范 Bean 的创建和管理。

Component, Service, Configuration 负责各自功能
- @Component 推荐声明到工具类前,@Component 内部不能再嵌
@Autowired
。
@Component
public class CryptoTool {}
- @Configuration 推荐声明到配置类前
@Configuration
public class MyConfig {
@Bean
String ipv4Address () {
return "0.0.0.0";
}
@Autowired
CryptoTool cryptoTool;
}
- @Service 推荐声明到业务处理类前
@Service
public class UserService {
@Autowired
CryptoTool cryptoTool;
@Autowired
MyConfig myConfig;
// CRUD
}
Bean 的生命周期
Bean 默认一直存在 Spring 容器里面,供多个类引用。但是 Bean 也可以指定声明周期。
用 @Scope
声明 Bean 的生命周期:
@Component
class MyComponent {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
String ipv4Address () {
return "0.0.0.0";
}
}
@Scope 值 | 描述 |
---|---|
SCOPE_SINGLETON | @Autowired 每次装配的都是同一个对象 |
SCOPE_PROTOTYPE | @Autowired 每次装配都会新建一个对象 |
用 ID 区分 Bean
刚才声明的 Bean 有一个问题,@Autowired 时根据类型装配的。如果声明多个 String Bean,则 Spring 不知道应该用哪个 Bean 装配,于是报错。
@Component
class MyComponent {
@Bean
String ipv4Address () {
return "0.0.0.0";
}
@Bean
String ipv6Address () {
return "::";
}
}
@Configuration
class MyConfig {
@Autowired
String inetAddress;
// 报错 expected single matching bean but found 2: ipv4Address,ipv6Address
}
给 Bean 定义 ID 可以按要求装配:
@Component
class MyComponent {
@Bean
@Qualifier("ipv4_addr")
String ipv4Address () {
return "0.0.0.0";
}
@Bean
@Qualifier("ipv6_addr")
String ipv6Address () {
return "::";
}
}
再通过 Bean ID 装配对应的 bean:
@Configuration
class MyConfig {
@Autowired
@Qualifier("ipv4_addr")
String ipv4Address;
@Autowired
@Qualifier("ipv6_addr")
String ipv6Address;
}
从配置文件装配
Spring 约定自动读取的配置文件路径: src/main/resources/application.properties
或是 YAML 格式: src/main/resources/application.yml
以下2个配置文件任选一个,它们的效果是一样的,推荐 yaml 格式
myconfig.port=9662
myconfig:
port: 9662
按上面的配置文件,装配时用 @Value
注入,将自动装上 9662
:
@Configuration
class MyConfig {
@Autowired
@Value("${myconfig.port}") // 9662,装上!
Integer port;
}
配置文件再从环境变量读取
部署容器程序经常用到环境变量,Spring 配置文件也支持从环境变量读取。
myconfig:
port: ${PORT}
使用时,指定环境变量:
export PORT=9663
小结
Spring 项目不可避免地用到很多 Bean。但简单来说,Bean 只是的另一种管理对象的方式而已。
利用 Bean 可以免去很多手写代码的情形,像 new
一堆东西,然后赋值,又或者 try catch 一个读配置文件的操作,用 Autowired、Value 就可以解决了。
附录和引用
实际操作可能有遇到很多的问题,将自己遇到的简单整理一下。
定义了 Service 但是 Service 是在另一个包导致没有创建对应的 Bean (StackOverflow)
@SpringBootApplication 默认是扫描 @TA 的程序所在的包
import org.luo3.myapp;
@SpringBootApplication
class MyApp {}
- SpringBootApplication 将扫描
org.luo3.myapp.*
的类并创建所需的 Bean。 - SpringBootApplication 不扫描
org.luo4.myapp.*
。 - SpringBootApplication 不扫描
org.luo3.yourapp.*
。
import org.luo3.myapp;
@SpringBootApplication(scanBasePackages={"org.luo3.myapp", "org.luo3.otherpackage"})
class MyApp {}