在之前的文章 Java知识点整理 8 — Spring 简介 中介绍了 Spring 的两大核心概念 IoC 和 AOP,但对 Spring Bean 的介绍不全面,本文将补充 Spring 中 Bean 的概念。
一. 什么是 Spring Bean
在 Spring 官方文档中,对 bean 的定义为:构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。
开发者需要告诉 IoC 容器协助管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML文件(较老)、注解或者 Java 配置类。
二. 通过例子来理解
首先有一个 Student 类,里面有两个成员变量 id 和 name,并提供 set、get方法。
public class Student {private Integer id;private String name;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
另一个类是 StudentManager,有一个 Student 的对象,提供了 setStudent 方法初始化 Student 对象,并且它的 show 方法能够打印这个 student 的 id 和 name。
public class StudentManager {private Student student;public void setStudent(Student student) {this.student = student;}public void show() {System.out.println(student.getId());System.out.println(student.getName());}
}
分析以上两段代码发现,两个类之间高度耦合,后者依赖于前者。假如没有及时对 StudentManager 的 student 绑定对象,却调用了 show 方法,那么程序就会报空指针异常的错误。因此 Spring 提供了 IoC(控制反转)和 DI(依赖注入)进行解耦。
在 Spring 中不需要自己创建对象,只需要告诉 Spring,哪些类需要创建,然后在启动项目时 Spring 就会自动帮助创建对应的对象,并且只存在一个类的实例。这个类的实例也就是 Bean,而这种模式通常称为单例模式,即一个类只有一个实例。
继续思考,开发者该如何告诉 Spring 哪些类需要创建对象呢?
最简单最常用的方式就是 Java 注解配置。也就是将一个类声明为 Bean 所需要的注解。
声明 | 含义 |
@Component | 通用注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用该注解标注 |
@Repository | 当前类在持久层(Dao层),主要用于数据库相关操作。 |
@Service | 当前类在业务逻辑层,主要涉及复杂的逻辑。 |
@Controller | 当前类在控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。 |
其实以上四种声明方式效果完全一致,使用不同的关键词是让开发者快速了解该类属于哪一层。
例如,在刚才的 Student 类前加上 @Component 注解,就告诉 Spring:你要在项目创建运行时帮我创建 Student 类的 Bean(对象)。
@Component
public class Student {private Integer id;private String name;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
此时"依赖"添加完毕,但还没结束。虽然让 Spring 帮助我们创建了对象,但 StudentManager 怎么知道这个被创建的对象在哪呢?所以接下来要告诉 StudentManager 刚才 Spring 帮助创建的 Bean(对象)在哪,也就是注入 Bean。
注入注解 | |
声明 | 含义 |
@Autowired | 根据 Bean 的 Class 类型自动装配 |
@Inject | 字面意思注入 |
@Resource | 字面意思资源,根据 Bean 的属性名称(id / name)自动装配 |
例如,在 StudentManager 类中声明成员变量 Student 的前面加上 @Autowired,Spring 会自动注入一个 Bean。
@Component
public class StudentManager {@Autowiredprivate Student student;public void show() {System.out.println(student.getId());System.out.println(student.getName());}
}
三. @Bean 注解的使用
- @Bean 注解作用在方法上,产生一个 Bean 对象,然后将其交给 Spring 管理。Spring 会将这个 Bean 对象放在自己的 IoC 容器中。
- @Bean 方法名与返回类名一致,首字母小写。
- @Component、@Repository、@Controller、@Service 这些注解只局限于自己编写的类,@Bean 注解能把第三方库中的类实例加入 IoC 容器并交给 Spring 管理。
- @Bean 一般和 @Component 或 @Configuration 一起使用。
四. @Component 和 @Bean 的区别
- @Component 注解作用于类,@Bean 作用于方法。
- @Component 通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中,可以使用 @ComponentScan 注解定义要扫描的路径,从中找出标识了需要装配的类,并自动装配到 Spring 的 bean 容器中。@Bean 注解通常是在标有该注解的方法中定义产生这个 bean,它告诉了 Spring 这是某个类的实例,在需要时给我。
- @Bean 注解比 @Component 注解的自定义性更强,而且很多地方只能通过@Bean 注解来注册 bean,比如第三方库。
@Bean 注解的使用:
@Configuration //标记该类为配置类,提供配置信息给 Spring 容器。
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}
}
五. @Autowired 和 @Resource 的区别
@Autowired 属于 Spring 内置的注解,默认注入方式为 byType,优先根据接口类型去匹配并注入 Bean(接口的实现类)。
但如果一个接口存在多个实现类,byType 这种方式可能无法正确注入对象,因为 Spring 找到了多个满足条件的选择,默认情况下不知道选哪一个。这种情况下,注入方式会变为 byName,即根据名称匹配,这个名称通常是类名,如下面的 SmsService。
// smsService 就是上面所说的名称
@Autowired
private SmsService smsService;
如果 SmsService 接口有两个实现类:SmsServiceImpl1 和 SmsServiceImpl2,且它们都被 Spring 容器管理。
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
通过 @Qualifier 注解能够显式指定名称,而不是依赖变量名称。
@Resource 属于JDK提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean,注入方式会变为 byType。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)和 type(类型)。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
总结一下:
- @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
- @Autowired 默认的注入方式为 byType,@Resource 默认的注入方式为 byName。
- 当一个接口存在多个实现类时,两个注解都需要通过名称才能正确匹配到对应的 Bean。@Autowired 可以通过 @Qualifier 注解来显示指定名称,@Resource 可以通过 name 属性来显式指定名称。
- @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
六. Bean 的作用域
通常有以下几种:
作用域 | 含义 |
singleton(默认) | IoC容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,单例模式的应用。 |
prototype | 每次获取都会创建一个新的 bean 实例。如果连续 getBean() 两次,得到的是不同的 Bean 实例。 |
request(仅 Web 应用) | 每次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。 |
session(仅 Web 应用) | 每次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。 |
application(仅 Web 应用) | 每个 Web 应用在启动时创建一个 bean(应用 bean),该 bean 仅在当前应用启动时间内有效。 |
websocket(仅 Web 应用) | 每次 WebSocket 会话产生一个新的 bean。 |
如何配置:
// 注解方式
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {return new Person();
}
七. Bean 的生命周期
1. 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
2. Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如 @Autowired 等注解注入对象、@Value 注入值、@Resource 注入各种资源等。
3. Bean 初始化:
- 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName() 方法,传入 Bean 的名字。
- 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader() 方法,传入 ClassLoader 对象的实例。
- 与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
- 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
- 如果 Bean 实现了 InitializingBean 接口,执afterPropertiesSet() 方法。
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。
4. 销毁 Bean:销毁并不是说立刻把 Bean 销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
- 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
- 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过 @PreDestroy 注解标记 Bean 销毁之前执行的方法。