Spring Bean
@[toc]
---
## 什么是 Spring Bean?
交给 Spring 管理的对象也称为 `Bean`,所有的 Bean 都被存储在 IoC 容器中
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类

## 创建 Spring Bean 的方法有哪些
### 使用构造函数创建 Bean
可以通过在配置类中使用 `@Bean` 注解,并提供相应的构造函数参数,来创建 Bean 实例。示例代码如下:
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean(); // 使用默认的无参构造函数创建 Bean 实例
}
@Bean
public AnotherBean anotherBean() {
return new AnotherBean(myBean()); // 通过构造函数注入依赖的 Bean
}
}
```
### 使用静态工厂方法创建 Bean
可以通过在配置类中使用 `@Bean` 注解,并提供一个工厂方法(静态工厂方法)来创建 Bean 实例。示例代码如下:
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean() {
return MyBeanFactory.createMyBean(); // 调用工厂方法创建 Bean 实例
}
}
public class MyBeanFactory {
public static MyBean createMyBean() {
return new MyBean(); // 工厂方法创建 Bean 实例
}
}
```
### 使用实例工厂方法创建 Bean
如果 Bean 的创建依赖于其他 Bean 实例,可以使用实例工厂方法创建 Bean。示例代码如下:
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBeanFactory myBeanFactory() {
return new MyBeanFactory(); // 实例工厂方法
}
@Bean
public MyBean myBean() {
return myBeanFactory().createMyBean(); // 调用实例工厂方法创建 Bean 实例
}
}
public class MyBeanFactory {
public MyBean createMyBean() {
return new MyBean(); // 实例工厂方法创建 Bean 实例
}
}
```
### 使用注解方式创建 Bean
可以使用 `@Component` 注解或其衍生注解(`@Service`、`@Repository`、`@Controller`)标记类,并让 Spring 自动扫描并创建 Bean 实例:
- `@Component` :通用的注解,可标注任意类为 `Spring` 组件。如果一个 Bean 不知道属于哪个层,可以使用`@Component` 注解标注。
- `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。
- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
- `@Controller` : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面
这些注解的作用完全一样,只不过方便标注在不同层
示例代码如下:
```java
@Component
public class MyBean {
// Bean 的实现
}
```
在 Spring 的配置文件中,需要配置组件扫描的基础包,以便 Spring 能够扫描并创建被标记的类。
## @Component 和 @Bean 的区别是什么?
- **`@Component` 注解作用于类,而 `@Bean` 注解作用于方法**
- `@Component`通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。
`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 Bean,`@Bean` 告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
- **`@Bean` 注解比 `@Component` 注解的自定义性更强**,而且很多地方我们只能通过 `@Bean` 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean` 来实现。
`@Bean` 注解使用示例:
```java
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
```
上面的代码相当于下面的 xml 配置
```xml
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
```
下面这个例子是通过 `@Component` 无法实现的。
```java
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
```
## 注入 Bean 的注解有哪些?
Spring 内置的 `@Autowired` 以及 JDK 内置的 `@Resource` 和 `@Inject` 都可以用于注入 Bean。
| Annotaion | Package | Source |
| ------------ | ---------------------------------- | ------------ |
| `@Autowired` | `org.springframework.bean.factory` | Spring 2.5+ |
| `@Resource` | `javax.annotation` | Java JSR-250 |
| `@Inject` | `javax.inject` | Java JSR-330 |
`@Autowired` 和 `@Resource` 使用的比较多一些
## @Autowired 和 @Resource 的区别是什么?
`Autowired` 属于 Spring 内置的注解,**默认的注入方式为 `byType`(根据属性类型进行匹配)**,也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
当一个接口存在多个实现类的话,`byType` 这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。**这种情况下,注入方式会变为 `byName`(根据属性名称进行匹配)**,这个名称通常就是类名(首字母小写)。就比如说下面代码中的 `smsService` 就是我这里所说的名称
```java
// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;
```
> 举个例子,`SmsService` 接口有两个实现类: `SmsServiceImpl1`和 `SmsServiceImpl2`,且它们都已经被 Spring 容器所管理。
>
> ```java
> // 报错,byType 有多个且 byName 无法匹配
> @Autowired
> private SmsService smsService;
>
> // 正确注入 SmsServiceImpl1
> @Autowired
> private SmsService smsServiceImpl1;
>
> // 正确注入 SmsServiceImpl1
> // 通过 Qualifier 指定 name = "smsServiceImpl1"
> @Autowired
> @Qualifier(value = "smsServiceImpl1")
> private SmsService smsService;
> ```
>
> 我们还是建议通过 `@Qualifier` 注解来显示指定名称而不是依赖变量的名称。
`@Resource` 属于 JDK 提供的注解,和 @Autowired 恰恰相反,它的**默认注入方式为 `byName`。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 `byType`**。
`@Resource` 有两个比较重要且日常开发常用的属性:`name`(名称)、`type`(类型)。
```java
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
```
如果仅指定 `name` 属性则注入方式为 `byName`,如果仅指定 `type` 属性则注入方式为 `byType`,如果同时指定`name` 和`type`属性(不建议这么做)则注入方式为 `byType` + `byName`。
> 举个例子,`SmsService` 接口有两个实现类: `SmsServiceImpl1`和 `SmsServiceImpl2`,且它们都已经被 Spring 容器所管理。
>
> ```java
> // 报错,byName 和 byType 都无法匹配到 bean
> @Resource
> private SmsService smsService;
>
> // 正确注入 SmsServiceImpl1
> @Resource
> private SmsService smsServiceImpl1;
>
> // 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
> @Resource(name = "smsServiceImpl1")
> private SmsService smsService;
> ```
简单总结一下:
- `@Autowired` 是 Spring 提供的注解,`@Resource` 是 JDK 提供的注解。
- `Autowired` 默认的注入方式为 `byType`(根据属性类型进行匹配),如果查不到则再根据属性名称进行匹配 `byName`
`@Resource` 默认注入方式为 `byName`,如果查不到则再根据属性类型进行匹配 `byType`
- 当一个接口存在多个实现类的情况下,`@Autowired` 和 `@Resource` 都需要通过名称才能正确匹配到对应的 Bean。`Autowired` 可以通过 `@Qualifier` 注解来显示指定名称,`@Resource` 可以直接通过内置的 `name` 属性来显示指定名称
## Bean 作用域
在定义 Bean 时,用户不但可以配置 Bean 的属性值及相互之间的依赖关系,还可以定义 Bean 的作用域。**作用域将对 Bean 的生命周期和创建方式产生影响。**
> **如何配置 bean 的作用域呢?**
>
> xml 方式:
>
> ```xml
> <bean id="..." class="..." scope="singleton"></bean>
> ```
>
> 注解方式:
>
> ```java
> @Bean
> @Scope("singleton")
> public Person personPrototype() {
> return new Person();
> }
> ```
Bean 的作用域如下 ⬇️
### singleton(默认)
singleton 的 Bean 在同一 Spring 容器中只有一个实例,Spring 中的 bean 默认都是单例的,属于单例设计模式的应用
> 举个例子:
>
> ```xml
> <bean id="car" class="com.smart.scope.Car" scope="singleton"/> 1
> <bean id="bossl" class="com.smart.scope.Boss" p: car-ref="car"/> 2
> <bean id="boss2" class="com.smart.scope.Boss" p: car-ref="car"/> 3
> <bean id="boss3" class="com.smart.scope.Boss" p: car-ref="car"/> 4
> ```
>
> 1 处的 car Bean 声明为 singleton(**默认是 singleton,所以其实无须显式指定**),在容器中有 3 个其他的 Bean 引用了 car Bean,如 2、3、4 所示。在容器内部,boss1、boss2 和 boss3 的 car 属性都指向同一个 Bean
>
> <img src="https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/image-20220708152040737.png" style="zoom:67%;" />
需要注意的是,**只有作用域为 Singleton 的 Bean 由 Spring 容器负责管理和维护,而其他作用域的 Bean 需要在适当的时候手动管理其创建和销毁**。
### prototype
每次通过容器的 getBean 方法返回的都是一个新的 bean 实例。在默认情况下,Spring 容器在启动时不实例化 prototype 的 Bean。此外,Spring 容器将 prototype的 Bean 交给调用者后,就不再管理它的生命周期
> 举个例子:
>
> ```xml
> <bean id="car" class="com.smart.scope.Car" scope="prototype"/> 1
> <bean id="bossl" class="com.smart.scope.Boss" p: car-ref="car"/> 2
> <bean id="boss2" class="com.smart.scope.Boss" p: car-ref="car"/> 3
> <bean id="boss3" class="com.smart.scope.Boss" p: car-ref="car"/> 4
> ```
>
> <img src="https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/image-20220708152149157.png" style="zoom:50%;" />
### request
每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
### session
每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
假设我们有一个用户登录的功能,需要在用户的整个会话期间保持用户的登录状态。
在这种情况下,可以将用户对象的作用域设置为 Session,以便在同一个用户的不同请求之间共享同一个用户对象。这样可以确保用户的登录状态在整个会话期间保持一致。
```java
@Component
@Scope("session")
public class User {
// ...
}
```
### global-session
全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。
## 单例 Bean 的线程安全问题及解决方案
### 单例 Bean 的线程安全问题
先来解释下线程安全和**无状态**、**有状态**:
- 所谓无状态就是不会保存数据(不存在写数据,当然也就不存在线程安全问题了)
- 而有状态就是有数据存储功能(就是除了读数据它还写数据了),可以这样讲,线程安全问题都是由全局变量及静态变量引起的。换句话说,**若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的**),比如 controller 中存着一个可变的全局成员变量,如下例:
```java
@RestController
public class TestController {
// 一个非线程安全变量(全局变量)
private int var = 0;
@GetMapping(value = "/test_var")
public String test() {
var ++;
System.out.println("普通变量 var:" + var);
return "普通变量 var:" + var ;
}
}
```
当多个用户同时请求一个服务器时,tomcat 会给每一个请求分配一个线程,这时多个线程会并发执行该请求所对应的业务逻辑(controller里的方法)
在 postman 里面发三次请求,结果如下:
```
普通变量 var:1
普通变量 var:2
普通变量 var:3
```
说明他不是线程安全的。
而 Controller、Service 和 Dao 层本身因为可以**携带状态**所以并不是线程安全的!!!只不过我们实际开发中很少去定义这种非线程安全的变量罢了(也就是我们常说的大部分 Bean 其实都是无状态的,所以在某种程度上来说 Bean 其实是安全的)。
Spring 中的 Bean 默认是单例模式的,框架并没有对 Bean 进行多线程的封装处理,那么,如果遇到这种情况,该怎么办呢?
### 解决方案
1)**更换 Bean 的作用域为原型(多例)模式 `@scope("prototype")`**,这样每次请求 Bean 就相当于是 new Bean() 这样就可以保证线程的安全了
> 还是上面那个例子:
>
> ```java
> @RestController
> @Scope(value = "prototype")
> public class TestController {
>
> // 一个非线程安全变量(全局变量)
> private int var = 0;
>
> @GetMapping(value = "/test_var")
> public String test() {
> var ++;
> System.out.println("普通变量 var:" + var);
> return "普通变量 var:" + var ;
> }
> }
> ```
>
> 这样一来,每个请求都单独创建一个Controller容器,所以各个请求之间是线程安全的,三次请求结果:
>
> ```
> 普通变量 var:1
> 普通变量 var:1
> 普通变量 var:1
> ```
>
> 那么,多实例 Bean 是不是一定就线程安全呢?
>
> ```java
> @RestController
> @Scope(value = "prototype")
> public class TestController {
> private int var = 0;
> private static int staticVar = 0;
>
> @GetMapping(value = "/test_var")
> public String test() {
> var ++;
> staticVar ++;
> System.out.println("普通变量 var:" + var + ",静态变量 staticVar:" + staticVar);
> return "普通变量 var:" + var + "静态变量 staticVar:" + staticVar;
> }
> }
> ```
>
> 看三次请求结果:
>
> ```
> 普通变量 var:1,静态变量 staticVar:1
> 普通变量 var:1,静态变量 staticVar:2
> 普通变量 var:1,静态变量 staticVar:3
> ```
>
> **静态变量始终是只有一份的,即便是多例 Bean,也是非线程安全的**
>
> **所以,尽量不要在 Controller/Service 等容器中定义静态变量,不论是单例还是多例,他都是线程不安全的**
2)**使用 ThreadLocal**
可以将一些非线程安全的变量以 ThreadLocal 存放,在同一次请求响应的调用线程中,所有对象所访问的同一 ThreadLocal 变量都是当前线程所绑定的。
<img src="https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/image-20220708153912436.png" style="zoom: 67%;" />
> Spring 中事务底层 `TransactionSynchronizationManager` 就大量使用了 ThreadLocal
>
> ```java
> @RestController
> public class TestController {
> private int var = 0; // 定义一个普通变量
> ThreadLocal<Integer> var = new ThreadLocal<>(); // 用 ThreadLocal 来封装变量
> ```
## Bean 的生命周期
对于 Spring Bean 的生命周期来说,只有四个步骤:
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
整个生命周期相关的方法可以分类为以下三种:
- **Bean 自身的方法**:对应着上文说的实例化 -> 属性赋值 -> 初始化 -> 销毁四个阶段。
- **容器级生命周期方法**:主要是后处理器方法,比如 `InstantiationAwareBeanPostProcessor`、`BeanPostProcessor` 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。
- **Bean 级生命周期方法**:可以理解为 Bean 类直接实现接口的方法,比如 `BeanNameAware`、`BeanFactoryAware`、`ApplicationContextAware`、`InitializingBean`、`DisposableBean` 等方法,这些方法只对当前 Bean 生效
具体过程如下面两张图所示,配合文字食用(下图中的 postProcessProperties 改为 `postProcessPropertyValues`):

1. 当调用者通过 `getBean(beanName)` 向容器请求某一个 Bean 时,如果容器注册了 `InstantiationAwareBeanPostProcessor` 接口,则在实例化 Bean(调用 Bean 的构造函数) 之前,将调用接口的 `postProcessBeforeInstantiation()` 方法
2. ⭐ **实例化 Instantiation**:根据配置情况调用 Bean 构造函数或工厂方法实例化 Bean
3. 如果容器注册了 `InstantiationAwareBeanPostProcessor` 接口,那么在实例化Bean之后,调用该接口的 `postProcessAfterInstantiation()` 方法,可在这里对已经实例化的对象进行一些“梳妆打扮”
4. 随后,在设置每个属性之前将先调用 `InstantiationAwareBeanPostProcessor` 接口的 `postProcessPropertyValues()` 方法
5. ⭐ **属性赋值 Populate**:调用 Bean 的 set 方法【设置属性值】
6. 如果 Bean 实现了 `BeanNameAware` 接口,则将调用 setBeanName() 接口方法,将配置文件中该 Bean 对应的名称设置到 Bean 中
7. 如果 Bean 实现了 `BeanClassLoaderAware` 接口,则将调用 setClassLoader() 接口方法,将类加载器设置到 Bean 中
8. 如果 Bean 实现了 `BeanFactoryAware` 接口,则将调用 setBeanFactory() 接口方法,将 BeanFactory 容器实例设置到 Bean 中
9. 如果 BeanFactory 装配了 `BeanPostProcessor` 后处理器,则将调用 `BeanPostProcessor` 的 `Object postProcessBeforeInitialization(Object bean, String beanName)` 接口方法对 Bean 进行加工操作。
其中,入参 bean 是当前正在处理的 Bean,而 beanName 是当前 Bean 的配置名,返回的对象为加工处理后的 Bean。用户可以使用该方法对某些 Bean 进行特殊的处理,甚至改变 Bean 的行为。
> BeanPostProcessor 在 Spring 框架中占有重要的地位,为容器提供对 Bean 进行后续加工处理的切入点,Spring 容器所提供的各种 “神奇功能”(如 AOP、动态代理等)都通过 BeanPostProcessor 实施。
10. 如果 Bean 实现了 `InitializingBean` 接口,则将调用接口的 `afterPropertiesSet()` 方法
11. ⭐ **初始化 Initialization**:如果在 `<bean>` 中通过 `init-method` 属性定义了初始化方法,则将执行这个方法
12. `BeanPostProcessor` 后处理器定义了两个方法:其一是 postProcessBeforeInitialization(),在第(9)步调用;其二是 `Object postProcessAfterInitialization(Object bean,String beanName)`,这个方法在此时调用,容器再次获得对 Bean 进行加工处理的机会
13. 如果在 `<bean>` 中指定 Bean 的作用范围为 `scope="prototype"`,则将 Bean 返回给调用者,调用者负责 Bean 后续生命的管理,Spring 不再管理这个 Bean的 生命周期。
如果将作用范围设置为 `scope="singleton"`,则将 Bean 放入 Spring IoC 容器的缓存池中,并将 Bean 引用返回给调用者,Spring 继续对这些 Bean 进行后续的生命管理
14. ⭐ **销毁 Destruction**:对于 scope="singleton" 的 Bean(默认情况),当容器关闭时,将触发 Spring 对 Bean 后续生命周期的管理工作。如果 Bean 实现了 `DisposableBean` 接口,则将调用接口的 destory() 方法,可以在此编写释放资源、记录日志等操作;随后,如果通过 `<bean>` 的 `destroy-method` 属性指定了 Bean 的销毁方法,那么 Spring 将执行 Bean 的这个方法,完成 Bean 资源的释放等操作
再来看张图理解下(下图中的 **BeanFactoryPostProcessor(工厂后处理器)** 可以忽略,在 IoC 启动过程 `refresh()` 中有涉及到,这里不提它也无所谓):

精简版的图示如下:

