文章目录
- 1. 定义 Spring Bean
- 2. BeanDefinition元信息:除了Bean名称和类名,还有哪些Bean元信息值得关注?
- 3. 命名Spring Bean:id和name属性命名Bean,哪个更好?
- 4. Spring Bean的别名:为什么命名Bean还需要别名?
- 5. 注册Spring Bean:如何将BeanDefinition注册到IoC容器?
- 6. 实例化Spring Bean:Bean实例化的姿势有多少种?
- 7. 初始化Spring Bean:Bean初始化有哪些方式?
- 8. 延迟初始化Spring Bean:延迟初始化的Bean会影响依赖注入吗?
- 9.销毁Spring Bean: 销毁Bean的基本操作有哪些?
- 10. 回收Spring Bean:Spring IoC容器管理的Bean能够被垃圾回收吗?
- 11. 面试题
1. 定义 Spring Bean
- 什么是 BeanDefinition?
- BeanDefinition 是 Spring Framework 中定义 Bean 的配置元信息接口,包含:
- Bean 的类名
- Bean 行为配置元素,如作用域、自动绑定的模式,生命周期回调等
- 其他 Bean 引用,又可称作合作者(collaborators)或者依赖(dependencies)
- 配置设置,比如 Bean 属性(Properties)
2. BeanDefinition元信息:除了Bean名称和类名,还有哪些Bean元信息值得关注?
2.1 BeanDefinition 元信息
2.2 BeanDefinition 构建
- 通过 BeanDefinitionBuilder
- 通过 AbstractBeanDefinition 以及派生类
两者并没有本质区别,区别只是第一种应用了builder设计模式。
public class BeanDefinitionCreationDemo {
public static void main(String[] args) {
// 1.通过 BeanDefinitionBuilder 构建
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
// 通过属性设置
beanDefinitionBuilder
.addPropertyValue("id", 1)
.addPropertyValue("name", "小马哥");
// 获取 BeanDefinition 实例
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// BeanDefinition 并非 Bean 终态,可以自定义修改
// 2. 通过 AbstractBeanDefinition 以及派生类
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
// 设置 Bean 类型
genericBeanDefinition.setBeanClass(User.class);
// 通过 MutablePropertyValues 批量操作属性
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues
.add("id", 1)
.add("name", "小马哥");
// 通过 set MutablePropertyValues 批量操作属性
genericBeanDefinition.setPropertyValues(propertyValues);
}
}
3. 命名Spring Bean:id和name属性命名Bean,哪个更好?
3.1 Bean 的名称
每个 Bean 拥有一个或多个标识符(identifiers),这些标识符在 Bean 所在的容器必须是唯一的。通常,一个 Bean 仅有一个标识符,如果需要额外的,可考虑使用别名(Alias)来扩充。
在基于 XML 的配置元信息中,开发人员可用 id 或者 name 属性来规定 Bean 的 标识符。通常Bean 的 标识符由字母组成,允许出现特殊字符。如果要想引入 Bean 的别名的话,可在name 属性使用半角逗号(“,”)或分号(“;”) 来间隔。
Bean 的 id 或 name 属性并非必须制定,如果留空的话,容器会为 Bean 自动生成一个唯一的名称。Bean 的命名尽管没有限制,不过官方建议采用驼峰的方式,更符合 Java 的命名约定。
3.2 Bean 名称生成器(BeanNameGenerator)
- 由 Spring Framework 2.0.3 引入,框架內建两种实现:
DefaultBeanNameGenerator:默认通用 BeanNameGenerator 实现
源码:
public class DefaultBeanNameGenerator implements BeanNameGenerator {
/** 这种方式非常方便在其他类中使用,比如AbstractBeanDefinitionReader
* A convenient constant for a default {@code DefaultBeanNameGenerator} instance,
* as used for {@link AbstractBeanDefinitionReader} setup.
* @since 5.2
*/
// 单例的一种实现方式
public static final DefaultBeanNameGenerator INSTANCE = new DefaultBeanNameGenerator();
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
}
查看BeanDefinitionReaderUtils.generateBeanName(definition, registry)
public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
return generateBeanName(beanDefinition, registry, false);
}
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
String generatedBeanName = definition.getBeanClassName();
if (generatedBeanName == null) {
if (definition.getParentName() != null) {
generatedBeanName = definition.getParentName() + "$child";
}
else if (definition.getFactoryBeanName() != null) {
generatedBeanName = definition.getFactoryBeanName() + "$created";
}
}
if (!StringUtils.hasText(generatedBeanName)) {
throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
}
String id = generatedBeanName;
if (isInnerBean) {
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// Top-level bean: use plain class name with unique suffix if necessary.
return uniqueBeanName(generatedBeanName, registry);
}
return id;
}
uniqueBeanName(generatedBeanName, registry)实现有点意思
public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {
String id = beanName;
int counter = -1;
// Increase counter until the id is unique.
// 不停的循环直到生成独特的id
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
// GENERATED_BEAN_NAME_SEPARATOR 是#
id = beanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
return id;
}
- AnnotationBeanNameGenerator:基于注解扫描的 BeanNameGenerator 实现,起始于Spring Framework 2.5。
4. Spring Bean的别名:为什么命名Bean还需要别名?
<!-- 将 Spring 容器中 "user" Bean 关联/建立别名 - "xiaomage-user" -->
<alias name="user" alias="xiaomage-user" />
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-definitions-context.xml");
// 通过别名 xiaomage-user 获取曾用名 user 的 bean
User user = beanFactory.getBean("user", User.class);
User xiaomageUser = beanFactory.getBean("xiaomage-user", User.class);
System.out.println("xiaomage-user 是否与 user Bean 相同:" + (user == xiaomageUser));
}
注解@Bean也可以定义bean的别名。
5. 注册Spring Bean:如何将BeanDefinition注册到IoC容器?
5.1 BeanDefinition 注册
- XML 配置元信息
- <bean name=”…” … />
- Java 注解配置元信息
- @Bean
- @Component
- @Import
- Java API 配置元信息
- 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 非命名方式:
BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,BeanDefinitionRegistry) - 配置类方式:AnnotatedBeanDefinitionReader#register(Class…)
// 1.3 通过 @Import 来进行导入
@Import(AnnotationBeanDefinitionDemo.Config.class)
public class AnnotationBeanDefinitionDemo {
public static void main(String[] args) {
// 创建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 1.注册 Configuration Class(配置类)
applicationContext.register(AnnotationBeanDefinitionDemo.class);
// 2.通过 BeanDefinition 注册 API 实现
// 2.1 命名 Bean 的注册方式
registerUserBeanDefinition(applicationContext, "mercyblitz-user");
// 2.2 非命名 Bean 的注册方法
registerUserBeanDefinition(applicationContext);
// 启动 Spring 应用上下文
applicationContext.refresh();
// 按照类型依赖查找
System.out.println("Config 类型的所有 Beans" + applicationContext.getBeansOfType(Config.class));
System.out.println("User 类型的所有 Beans" + applicationContext.getBeansOfType(User.class));
// 显示地关闭 Spring 应用上下文
applicationContext.close();
}
public static void registerUserBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder beanDefinitionBuilder = genericBeanDefinition(User.class);
beanDefinitionBuilder
.addPropertyValue("id", 1L)
.addPropertyValue("name", "小马哥");
// 判断如果 beanName 参数存在时
if (StringUtils.hasText(beanName)) {
// 注册 BeanDefinition
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
} else {
// 非命名 Bean 注册方法
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinitionBuilder.getBeanDefinition(), registry);
}
}
public static void registerUserBeanDefinition(BeanDefinitionRegistry registry) {
registerUserBeanDefinition(registry, null);
}
// 1.2 通过 @Component 方式
@Component // 定义当前类作为 Spring Bean(组件)
public static class Config {
// 1.1 通过 @Bean 方式定义
/**
* 通过 Java 注解的方式,定义了一个 Bean
*/
@Bean(name = {"user", "xiaomage-user"})
public User user() {
User user = new User();
user.setId(2L);
user.setName("小马哥");
return user;
}
}
}
5.2 外部单例对象注册
- Java API 配置元信息
SingletonBeanRegistry#registerSingleton
6. 实例化Spring Bean:Bean实例化的姿势有多少种?
6.1 常规方式
- 通过构造器(配置元信息:XML、Java 注解和 Java API )
- 通过静态工厂方法(配置元信息:XML 和 Java API )
- 通过 Bean 工厂方法(配置元信息:XML和 Java API )
- 通过 FactoryBean(配置元信息:XML、Java 注解和 Java API )
<!-- 静态方法实例化 Bean -->
<bean id="user-by-static-method" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User"
factory-method="createUser"/>
<!-- 实例(Bean)方法实例化 Bean -->
<bean id="userFactory" class="org.geekbang.thinking.in.spring.bean.factory.DefaultUserFactory"/>
<bean id="user-by-instance-method" factory-bean="userFactory" factory-method="createUser"/>
<!-- FactoryBean实例化 Bean -->
<bean id="user-by-factory-bean" class="org.geekbang.thinking.in.spring.bean.factory.UserFactoryBean"/>
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");
User user = beanFactory.getBean("user-by-static-method", User.class);
User userByInstanceMethod = beanFactory.getBean("user-by-instance-method", User.class);
User userByFactoryBean = beanFactory.getBean("user-by-factory-bean", User.class);
System.out.println(user);
System.out.println(userByInstanceMethod);
System.out.println(userByFactoryBean);
System.out.println(user == userByInstanceMethod);
System.out.println(user == userByFactoryBean);
}
6.2 特殊方式
- 通过 ServiceLoaderFactoryBean(配置元信息:XML、Java 注解和 Java API )
- 通过 AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
- 通过 BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
7. 初始化Spring Bean:Bean初始化有哪些方式?
- @PostConstruct 标注方法
- 实现 InitializingBean 接口的 afterPropertiesSet() 方法
- 自定义初始化方法
- XML 配置:<bean init-method=”init” … />
- Java 注解:@Bean(initMethod=”init”)
- Java API:AbstractBeanDefinition#setInitMethodName(String)
思考:假设以上三种方式均在同一 Bean 中定义,那么这些方法的执行顺序是怎样? 按照上述顺序初始化。
8. 延迟初始化Spring Bean:延迟初始化的Bean会影响依赖注入吗?
Bean 延迟初始化(Lazy Initialization)
- XML 配置:<bean lazy-init=”true” … />
- Java 注解:@Lazy()
思考:当某个 Bean 定义为延迟初始化,那么,Spring 容器返回的对象与非延迟的对象存在怎样的差异?
9.销毁Spring Bean: 销毁Bean的基本操作有哪些?
Bean 销毁(Destroy)
- @PreDestroy 标注方法
- 实现 DisposableBean 接口的 destroy() 方法
- 自定义销毁方法
- XML 配置:<bean destroy=”destroy” … />
- Java 注解:@Bean(destroy=”destroy”)
- Java API:AbstractBeanDefinition#setDestroyMethodName(String)
思考:假设以上三种方式均在同一 Bean 中定义,那么这些方法的执行顺序是怎样? 按照以上顺序。
10. 回收Spring Bean:Spring IoC容器管理的Bean能够被垃圾回收吗?
Bean 垃圾回收(GC)
- 关闭 Spring 容器(应用上下文)
- 执行 GC
- Spring Bean 覆盖的 finalize() 方法被回调
11. 面试题
- 如何注册一个 Spring Bean?
- 什么是 Spring BeanDefinition?
- Spring 容器是怎样管理注册 Bean