Spring-SpringBean
Spring-SpringBean
学习核心
- SpringBean概念基础核心
- 注入Bean的方式、注解有哪些?
- 注解的区别对比:
- @Component VS @Bean
- @Autowire VS @Resource
- Bean的作用域
学习资料
- SSM全家桶面试题 todo
SpringBean概念核心
1.BeanDefinition
Spring IoC 容器本身,并不能识别配置的元数据。为此,要将这些配置信息转为 Spring 能识别的格式——BeanDefinition
对象
BeanDefinition
是 Spring 中定义 Bean 的配置元信息接口,它包含:
- Bean 类名
- Bean 行为配置元素(作用域、自动绑定的模式、生命周期回调等)
- 其他 Bean 引用,也可称为合作者(Collaborators)或依赖(Dependencies)
- 配置设置(Bean 属性(Properties)等)
BeanDefinition元信息
BeanDefinition
元信息如下:
属性(Property) | 说明 |
---|---|
Class | 全类名(必须是具体类,不能用抽象类或接口) |
Name | Bean 的名称或者 ID |
Scope | Bean 的作用域(如:singleton 、prototype 等) |
Constructor arguments | Bean 构造器参数(用于依赖注入) |
Properties | Bean 属性设置(用于依赖注入) |
Autowiring mode | Bean 自动绑定模式(如:通过名称 byName) |
Lazy initialization mode | Bean 延迟初始化模式(延迟和非延迟) |
Initialization method | Bean 初始化回调方法名称 |
Destruction method | Bean 销毁回调方法名称 |
BeanDefinition构建
构建方式:
- 通过
BeanDefinitionBuilder
- 通过
AbstractBeanDefinition
以及派生类
2.SpringBean命名
命名规则
每个 Bean 拥有一个或多个标识符(identifiers),这些标识符在 Bean 所在的容器必须是唯一的。通常,一个 Bean 仅有一个标识符,如果需要额外的,可考虑使用别名(Alias)来扩充。
在基于 XML 的配置元信息中,开发人员可以使用 id
属性、name
属性或来指定 Bean 标识符。通常,Bean 的标识符由字母组成,允许出现特殊字符。如果要想引入 Bean 的别名的话,可在 name
属性使用半角逗号(“,”)或分号(“;”) 来间隔。
Spring 中,为 Bean 指定 id
和 name
属性不是必须的。如果不指定,Spring 会自动为 Bean 分配一个唯一的名称。尽管 Bean 的命名没有限制,不过官方建议采用驼峰命名法来命名 Bean
SpringBean命名生成器
Spring 提供了两种 Spring Bean 命名生成器:
DefaultBeanNameGenerator
:默认通用BeanNameGenerator
实现。AnnotationBeanNameGenerator
:基于注解扫描的BeanNameGenerator
实现。
public interface BeanNameGenerator {
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
Spring Bean别名
Spring 支持通过 <alias>
属性为 Bean 设置别名
Bean 别名(Alias)的作用:
- 复用现有的
BeanDefinition
- 更具有场景化的命名方法,比如:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
<bean id="user" class="io.github.dunwu.spring.core.bean.entity.person.User">
<!-- 属性略 -->
</bean>
<alias name="user" alias="aliasUser" />
SpringBean生命周期
核心概念:实例化 =》属性赋值 =》 初始化 =》 使用 =》 销毁(记忆核心,展开扩展)
阶段说明 | |
---|---|
实例化(Instantiation) | Spring根据配置文件、注解等方式加载并创建Bean实例,将其存储到容器中 |
属性赋值(Populate Properties) | Spring将Bean的属性值从配置文件、注解等方式加载并注入到Bean实例中 |
初始化(Initialization) | Spring调用Bean实例的init-method方法,完成一些初始化操作(建立数据库连接等) |
使用(In User) | Bean实例初始化完成可以供程序正常调用 |
销毁(Destruction) | Spring调用destory-method方法,完成资源释放和清理操作(例如关闭数据库链接) |
具体的实现方式可以通过实现BeanPostProcessor和BeanFactoryPostProcessor接口来进行扩展
其中,BeanPostProcessor接口定义了两个方法postProcessBeforelnitialization和postProcessAfterlnitialization,分别在Bean的初始化前后被调用,用于扩展Bean初始化的过程
BeanFactoryPostProcessor 接口则定义了一个方法postProcessBeanFactory,用于在Bean工厂实例化Bean定义后对其进行修改
扩展版
Spring的通过上述阶段进行管理,开发者可以通过实现相关接口和方法来扩展和定制Bean的创建和销毁过程,以满足各种业务需求。本质上就是扩展接口作相应的方法增强。
在Bean初始化操作前后扩展Bean的初始化过程:实现
BeanPostProcessor
接口的接口方法postProcessBeforelnitialization
:Bean初始化前执行postProcessAfterlnitialization
:Bean初始化后执行
扩展Bean的销毁过程
- 实现
DispostbleBean
接口,Spring会调用其destory方法(在 Bean 实例销毁前执行的方法)(作用与在配置文件中对 Bean 使用destory-method
属性的作用一样)
- 实现
理解记忆,先掌握流程核心,然后细化实现:
- 流程核心:实例化、属性赋值、初始化、使用、销毁
- 细化实现:
- 【初始化】阶段涉及到一些扩展项(例如对初始化方法的一些增强)
- 【销毁】阶段:注册相关销毁回调接口,然后通过
DisposableBean
和destory-method
进行销毁
SpringBean注册
SpringBean的注册实际上就是将BeanDefinition注册到IOC容器中(被IOC容器管理),传统有3种注册方式
1.XML配置
方式1:XML配置(XML配置元信息)
XML配置是Spring的传统配置方式,通过<bean>
标签配置元数据内容。其缺点在于如果JavaBean过多,则配置文件会让人眼花缭乱
使用时可通过ApplicationContext加载配置文件并使用
参考配置:创建HelloService接口、HelloServiceImpl接口实现
【1】接口定义
public interface HelloServie{
public void sayHello();
}
public class HelloServieImpl implements HelloService{
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public void sayHello() {
System.out.println("hello"+info);
}
}
【2】构建配置文件:resources/spring/applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.快速入门 ========================================================= -->
<!-- - bean:就是定义一个具体的类 可以是一个对象 id 属性 就是一个合法的标识符 class 是当前类的全名称 -->
<bean id="helloService" class="com.noob.spring.a_quickstart.HelloServiceImpl">
<property name="info" value="hello"></property>
</bean>
<!--1.end快速入门 ====================================================== -->
</beans>
【3】使用对象(区分传统方式和Spring方式)
public class HelloServiceTest {
public static void main(String[] args) {
/**
* 1.传统方式
*/
HelloServiceImpl helloServiceImpl = new HelloServiceImpl();
helloServiceImpl.setInfo("hello普通测试......");
helloServiceImpl.sayHello();
/**
* 2.spring方式(代码中加载配置文件并引用对象)
* 需要在核心配置文件中先配置相关内容
* Spring中使用工厂 +反射 +配置文件的方式 实例化这个对象
*/
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring/applicationContext.xml");
HelloService service =(HelloService) applicationContext.getBean("helloService");
service.sayHello();
}
}
2.注解配置
方式2:注解配置(注解配置元信息)
通过使用@Bean
、@Component
、@Import
等注解注册SpringBean
类似地还有@Repository
、@Service
、@Controller
(基于代码分层引入)
@Bean VS @Component
结合使用方式、作用范围、灵活性等方面阐述
@Component
作用于类,@Bean
作用于方法使用方式
@Component
结合@ComponentScan
使用:从指定扫描路径标识并自动装配到Spring的bean容器中;@Bean
则是在标有该注解的方法中定义产生bean,并告诉Spring这是某个类的实例,当需要用的时候还给我
灵活性
@Bean
注解比@Component
注解的自定义性更强。一些场景只能通过@Bean
注解来注册bean(例如引用第三方库中的类需要装配到Spring容器中时)
参考样例
# @Bean样例
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
# 上述配置等价于xml配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
# @Bean作用于方法样例
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
3.Java API配置
方式3:Java API配置元信息
- 命名方式:
BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 非命名方式:
BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,BeanDefinitionRegistry)
- 配置类方式:
AnnotatedBeanDefinitionReader#register(Class...)
SpringBean实例化
Spring Bean 实例化方式:
常规方式
- 通过构造器(配置元信息:XML、Java 注解和 Java API)
- 通过静态方法(配置元信息:XML、Java 注解和 Java API)
- 通过 Bean 工厂方法(配置元信息:XML、Java 注解和 Java API)
- 通过
FactoryBean
(配置元信息:XML、Java 注解和 Java API)
特殊方式
- 通过
ServiceLoaderFactoryBean
(配置元信息:XML、Java 注解和 Java API ) - 通过
AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
- 通过
BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 通过
SpringBean初始化和销毁
使用 @PostConstruct
和 @PreDestroy
注解分别指定相应的初始化方法和销毁方法
实现 InitializingBean
接口的 afterPropertiesSet()
方法来编写初始化方法;实现 DisposableBean
接口的 destroy()
方法来编写销毁方法
InitializingBean
接口包含一个afterPropertiesSet
方法,可以通过实现该接口,然后在这个方法中编写初始化逻辑DisposableBean
接口包含一个destory
方法,可以通过实现该接口,然后在这个方法中编写销毁逻辑
自定义初始化方法
- XML 配置:
<bean init-method="init" destroy="destroy" ... />
- Java 注解:
@Bean(initMethod = "init", destroyMethod = "destroy")
- Java API:
AbstractBeanDefinition#setInitMethodName(String)
和AbstractBeanDefinition#setDestroyMethodName(String)
分别定义初始化和销毁方法
注意:如果同时存在,执行顺序会按照序列执行
Bean 的延迟初始化
- xml 方式:
<bean lazy-init="true" ... />
- 注解方式:
@Lazy
SpringBean初始化前后的方法增强:实现BeanPostProcessor
接口的postProcessBeforeInitialization
和 postProcessAfterInitialization
postProcessBeforeInitialization
在组件的初始化方法调用之前执行postProcessAfterInitialization
在组件的初始化方法调用之后执行
SpringBean注入
区分注册概念:注册可以理解为定义(将对象注册到SpringIOC容器中,交由SpringIOC管理),注入理解为要引用对象的时候实例化
注入SpringBean的注解
Spring 内置的 @Autowired
以及 JDK 内置的 @Resource
和 @Inject
都可以用于注入 Bean,其中@Autowired
和@Resource
使用的比较多一些
Annotation | Package | Source |
---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-330 |
@Autowired
VS@Resource
结合注解归属包、注入方式、使用范围
分类\注解 | @Autowired | @Resource |
---|---|---|
注解归属包 | Spring 内置的注解 | JDK 提供的注解 |
注入方式 | 默认的注入方式为byType (根据类型进行匹配)优先根据接口类型去匹配并注入 Bean (接口的实现类) | 默认注入方式为 byName 如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 byType |
使用范围 | 支持在构造函数、方法、字段和参数上使用 | 主要用于字段和方法上的注入,不支持在构造函数或参数上使用 |
byType
方式可能存在的问题:当一个接口存在多个实现类的话,byType
这种方式就无法正确注入对象了,因为此时Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName
(根据名称进行匹配),这个名称通常就是类名(首字母小写)
【1】@Autowired
与@Qualifier
结合使用
// 场景:定义SsmService接口、接口实现类:SsmServiceImpl1、SsmServiceImpl2
// 因为SmsService有多个接口实现类,因此通过byType方式会获取到多个选择,这种情况下注入方式会转为byName方式
// 案例1:无法通过byType、byName获取(byType会发现有多个选择不知道用哪个转而使用byName,而此时对应实现默认名称为首字母小写应该是ssmServiceImpl1、ssmServiceImpl2)
@Autowired
private SmsService smsService;
// 案例2:正确注入(byType =》byName,此处根据变量名称匹配到了ssmServiceImpl1则正常注入)
@Autowired
private SmsService smsServiceImpl1;
// 案例3:正确注入,结合@Qualifier显式指定名称(而不依赖于变量的名称)
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
【2】@Resource
使用
@Resource
常用属性:name(名称)、type(类型)
- 如果仅指定name属性则注入方式为byName
- 如果仅指定type属性则注入方式为byType
- 如果指定name和type,注入方式为byName+byType(一般不建议)
// 报错,byName 和 byType 都无法匹配到 bean(默认先用byName无法找到,则转byTpe发现有多个选择则不符合)
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean(默认先用byName发现可以匹配到)
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)(这种方式明确指定name属性)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
SpringBean垃圾回收
SpringBean垃圾回收步骤
- 关闭Spring容器(应用上下文)
- 执行GC操作
- SpringBean覆盖的finalize()方法被回调
SpringBean作用范围
spring支持的bean作用域
- singleton(单例、默认):使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例
- prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例
- request(仅Web应用可用):该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationcontext环境
- session(仅Web应用可用):该属性仅用于HTTP Session,会话级别的对象,同一个Session共享一个Bean实例。不同Session使用不同的实例
- application/global-session(仅Web应用可用):该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例
- websocket(仅Web应用可用):每一次 WebSocket 会话产生一个新的 bean
其中request、session、global-session不是基础的Spring程序支持的,而是基于Web程序开发。Spring会整合SpringMVC进行web开发
Bean作用域的配置
【1】方式1:xml方式
<bean id="..." class="..." scope="singleton"></bean>
【2】方式2:注解方式
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}