4. Bean 基础
4.1 BeanDefinition 元信息
什么是 BeanDefinition ?
BeanDefinition 是 Spring 中定义 bean 的配置元信息接口,包含:
- bean 的类名
- bean 的行为,如作用域,自动绑定的模式,生命周期回调等
- 其他 bean 引用
- 配置设置,如 bean 属性 property
BeanDefinition 重要属性说明,具体参考AbstractBeanDefinition的属性
| 属性 | 说明 |
|---|---|
| beanClass | Bean 全类名,必须是具体类,不能是抽象类或接口 |
| scope | Bean 的作用域,如 singleton,prototype |
| constructorArgumentValues | Bean 构造器参数,用于构造器注入 |
| propertyValues | Bean 的属性与属性值映射,用于 Setter 注入 |
| autowireMode | Bean 自动绑定模式,通过 bean名称 byName,类型 byType |
| lazyInit | Bean 延迟初始化模式,延迟、非延迟 |
| initMethodName | Bean 初始化回调方法名称 |
| destroyMethodName | Bean 销毁回调方法名称 |
| factoryBeanName | Bean 工厂名称,factory-bean 指定 |
| factoryMethodName | Bean 工厂方法名称,factory-method 指定 |
| primary | |
| resource | |
| dependsOn |
需要注意的是并没有 bean 名称这个属性,因为注册 bean 时,key 为 bean 名称,value 为 bean 元信息 BeanDefinition,保存到 beanDefinitionMap中。
BeanDefinition 构建:1. 通过 BeanDefinitionBuilder 构建,2. 通过 AbstractBeanDefinition 派生类
- 通过 BeanDefinitionBuilder 构建
public static void main(String[] args) {
// 1. 通过 BeanDefinitionBuilder 构建
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(User.class)
.addPropertyValue("id", 1)
.addPropertyValue("name", "小马")
.setScope("singleton")
.getBeanDefinition();
System.out.println("BeanDefinitionBuilder 构造: " + beanDefinition);
}
输出结果:
BeanDefinitionBuilder 构造: Generic bean: class [org.geekbang.ioc.overview.lookup.domain.User]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
属性是否设置成功可以 debug 查看 propertyValues 属性
- 通过 AbstractBeanDefinition 派生类
public static void main(String[] args) {
// 2. 通过 AbstractBeanDefinition派生类构建bean
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
// 设置 bean 类型
genericBeanDefinition.setBeanClass(User.class);
// 设置属性
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("id", 1);
propertyValues.addPropertyValue("name", "安娜");
genericBeanDefinition.setPropertyValues(propertyValues);
System.out.println("genericBeanDefinition 构造: " + genericBeanDefinition);
}
输出结果:
genericBeanDefinition 构造: Generic bean: class [org.geekbang.ioc.overview.lookup.domain.User]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
4.2 Bean 的命名
每个 bean 都拥有一个或多个标识符(identifiers),这些标识符在 bean 所在的容器中必须是唯一的。通常,一个 bean 仅有一个标识符,额外的可以使用别名 Alias 来扩展。
在 xml 配置元信息中,可以用id或name属性类设置 bean 的标识符。若要引入 bean 的别名,可在 name 属性使用,来间隔。
bean 的 id 或 name 属性并非必须的,如未设置,容器会为 bean 自动生成一个唯一的名称。自动生成名称由 DefaultBeanNameGenerator 来完成
使用 @Component 注解标记 bean 通常都不用设置 bean 名称,自动生成名称由 AnnotationBeanNameGenerator 来完成
- xml 配置 bean 不设置名称,测试能否自动生成 bean 名称
<!-- bean 不设置名称, 测试能否自动生成 -->
<bean class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
// 测试 bean 自动生成的名称, xml 文件中未对 bean 设置名称
private static void testDefaultgenerate() {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-name-generator.xml");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
// 打印自动生成的 bean 名称
System.out.println("自动生成的 bean 名称: "+beanDefinitionNames[0]);
}
输出结果:
自动生成的 bean 名称: org.geekbang.ioc.overview.lookup.domain.User#0
源码分析:
DefaultBeanNameGenerator 会为未设置 bean 生成名称,使用 BeanDefinitionReaderUtils#generateBeanName() ,源码如下所示:
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
// 获取bean的全类名
String generatedBeanName = definition.getBeanClassName();
String id = generatedBeanName;
if (isInnerBean) {
// 内部类? 类名+#+16进制数
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// 类名+#+数字, 数字为该类在容器中产生了多个bean的编号
return uniqueBeanName(generatedBeanName, registry);
}
return id;
}
- 测试 @Component 标记的 bean 的生成名称
@Component
public class Student {
Integer id;
String name;
// setter, getter
}
private static void testAnnotationGenerate() {
// 创建并启动 applicationContext, 扫描指定包下的 @Component
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("org.geekbang.bean.definition");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
System.out.println("自动生成的 bean 名称: "+beanDefinitionNames[5]);
}
输出结果:
自动生成的 bean 名称: student
AnnotationBeanNameGenerator 会为未设置名称的 bean 自动生成名称,源码如下所示:
AnnotationBeanNameGenerator.java
protected String buildDefaultBeanName(BeanDefinition definition) {
// 获取bean的全类名
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
// 获取bean的类名
String shortClassName = ClassUtils.getShortName(beanClassName);
// 将类名转为首字母小写并返回
return Introspector.decapitalize(shortClassName);
}
- @Bean 的名称,默认为方法名
@Bean // 默认bean名称为方法名
public User getUser() {
return new User();
}
4.3 Bean 的别名
Bean 别名 Alias 的价值:
- 复用现有的 BeanDefinition
- 更具有场景化的命名方法,使用第三方 jar 时使用更加合适的名称
- 为 bean 配置别名
<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
<property name="id" value="1"/>
<property name="name" value="tracccer"/>
</bean>
<!-- 为容器中的bean "user" 设置别名 -->
<alias name="user" alias="mao-user"/>
- 测试从容器中通过别名获取 bean,判断是否为同一个 bean
public static void main(String[] args) {
// 1. 在 xml 文件中配置 bean
// 2. 启动spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean-definition-context.xml");
User user = beanFactory.getBean("user", User.class);
User maoUser = beanFactory.getBean("mao-user", User.class);
System.out.println("mao-user: " + maoUser);
System.out.println("user == maoUser: " + (user == maoUser));
}
输出结果:
mao-user: User{id=1, name='tracccer'}
user == maoUser: true
4.4 注册 bean
注册 bean 大致有 3 种方式:
- xml 配置元信息
<bean id="xx" class="com...." />
- java 注解配置元信息
- @Bean
- @Component
- @Import
- java api 配置元信息
- 手动设置 bean 名称:BeanDefinitionRegistry#registerBeanDefinition
- 自动生成 bean 名称:BeanDefinitionReaderUtils#registerWithGeneratedName
- 配置类方式:AnnotationConfigApplicationContext#register
- 外部对象注册到容器: ConfigurableListableBeanFactory#registerSingleton
-
@Import 注册 bean
// 补充
-
java api 注册bean,将 BeanDefinition 注册到 BeanDefinitionRegistry
public static void main(String[] args) {
// 1. 创建容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2. 注册配置类, 即代替xml配置
applicationContext.register(BeanDefintionRegisterDemo.class);
// 3. 启动应用上下文
applicationContext.refresh();
// 4.1 注册bean到容器, 手动设置bean名称
// AnnotationConfigApplicationContext是BeanDefinitionRegistry接口的实现类
registerUserBeanDefinition(applicationContext, "hanzo");
User hanzo = applicationContext.getBean("hanzo", User.class);
System.out.println("手动设置bean名称, hanzo: " + hanzo);
// 4.2 注册bean到容器, 自动生成bean名称
registerUserBeanDefinition(applicationContext, "");
Map<String, User> user = applicationContext.getBeansOfType(User.class);
// 自动生成的bean名称为 "org...User#0"
System.out.println("自动生成bean名称, user: " + user);
}
- 自动生成 bean 名称, 注册 bean 到容器,BeanDefinitionReaderUtils.registerWithGeneratedName()
- 手动设置 bean 名称,注册 bean 到容器,BeanDefinitionRegistry 是 BeanDefinition 注册中心,应用上下文 AnnotationConfigApplicationContext 也实现了该接口。BeanDefinitionRegistry.registerBeanDefinition()
private static void registerUserBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
// 1. 通过 BeanDefinitionBuilder 构建 bean
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(User.class)
.addPropertyValue("id", 1)
.addPropertyValue("name", "半藏")
.setScope("singleton").getBeanDefinition();
// 2. 注册bean到容器
if (StringUtils.isEmpty(beanName)) {
// 自动生成bean名称, 注册bean
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
} else {
// 手动设置bean名称, 注册bean到注册中心(应用上下文)
// AnnotationConfigApplicationContext应用上下文实现了BeanDefinitionRegistry接口
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
输出结果:
手动设置bean名称, hanzo: User{id=1, name='半藏'}
自动生成bean名称, user: {hanzo=User{id=1, name='半藏'}, org.geekbang.ioc.overview.lookup.domain.User#0=User{id=1, name='半藏'}}
- 配置类 Configuration 方式注册 bean 到容器,即 @Configuration + @Bean 的方式,AnnotationConfigApplicationContext.register(Config.class)
public static void main(String[] args) {
// 1. 创建容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2. 注册配置类, 即代替xml配置
applicationContext.register(Config.class);
// 3. 启动应用上下文
applicationContext.refresh();
// 4. 从容器获取bean
User genji = applicationContext.getBean("genji", User.class);
System.out.println(genji);
// 关闭应用上下文
applicationContext.close();
}
// 配置类, 类似于xml配置文件
public static class Config {
@Bean(name={"genji", "yuanshi"})
public User user() {
User user = new User();
user.setId(1L);
user.setName("源氏");
return user;
}
}
输出结果:
User{id=1, name='源氏'}
- 外部对象注册到容器,外部对象的生命周期并不由 Spring 容器来管理,但可以托管注册到 Spring 容器
public static void main(String[] args) {
// 1. 创建应用上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2. 创建一个外部 user 对象
User user = new User();
// 3. 注册外部对象到容器
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
beanFactory.registerSingleton("mercy", user);
// 4. 启动应用上下文
applicationContext.refresh();
// 5. 依赖查找
User mercy = applicationContext.getBean("mercy", User.class);
// 6. 判断容器中的 bean 是否是那个外部对象
System.out.println("mercy == user: " + (mercy == user));
// 7. 关闭应用上下文
applicationContext.close();
}
输出结果:
User{id=null, name='null'}
mercy == user: true
4.5 实例化 Bean
Bean 实例化 Instantiation 的 7 种方式:
- 4 种常规方式
- 通过构造器,配置元信息:xml,java 注解,java api
- 通过静态工厂方法,配置元信息:xml,java api
- 通过 bean 工厂方法,配置元信息:xml,java api
- 通过实现 FactoryBean 接口,配置元信息:xml,java 注解,java api
- 特殊方式
- 通过 ServiceLoaderFactoryBean,配置元信息:xml,java 注解,java api
- 通过 AutowireCapableBeanFactory#createBean(Class, int, boolean)
- 通过 BeanDefinitionRegistry#registryBeanDefinition(String, BeanDefinition)
1. 4 种常规方式实例化 Bean
-
通过构造器实例化 bean
// 补充
-
通过静态工厂方法实例化 bean,
factory-method="createUser"
首先在需要实例化的 pojo 类中设置静态工厂方法,关键点是静态方法,且必须在当前类中
public class User {
private Long id;
private String name;
// 静态工厂方法, 配置到xml factory-method, 通过该方法实例化 bean
public static User createUser() {
User user = new User();
user.setId(3L);
user.setName("Zarya");
return user;
}
}
在 xml 中配置bean,使用factory-method="createUser"指定静态工厂方法
<bean id="user-by-static-method" class="org.geekbang.ioc.overview.lookup.domain.User"
factory-method="createUser" />
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");
User user = applicationContext.getBean("user-by-static-method", User.class);
System.out.println("通过静态工厂方法创建bean: " + user);
}
输出结果
通过静态工厂方法创建bean: User{id=3, name='Zarya'}
-
通过 bean 工厂方法实例化bean
首先创建 bean 工厂,会通过该工厂的方法创建 bean
// bean工厂, 通过该工厂创建bean
public class DefaultUserFactory implements UserFactory{
public User createUser() {
User user = new User();
user.setId(5L);
user.setName("Echo");
return user;
}
}
bean 不需要配置 class,而是配置 bean 工厂factory-bean="userFactory",指定工厂方法factory-method="createUser",即上面的 java类
<!-- 通过bean工厂方法实例化 factory-bean -->
<bean id="user-by-factory-bean" factory-bean="userFactory" factory-method="createUser" />
<!-- 注册 bean 工厂到容器 -->
<bean id="userFactory" class="org.geekbang.bean.instantiation.DefaultUserFactory" />
从容器获取 bean 时,会自动使用 bean 中配置的 factory-bean对象,调用factory-method方法创建实例,并将其作为 bean 返回
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");
User user2 = applicationContext.getBean("user-by-factory-bean", User.class);
System.out.println("通过bean工厂方法创建bean: " + user2);
}
输出结果
通过bean工厂方法创建bean: User{id=5, name='Echo'}
- 通过实现 FactoryBean 接口实例化 bean
- 在 xml 中配置 FactoryBean 接口的实现类,将 bean 注册到容器
public class UserFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
User user = new User();
user.setId(6L);
user.setName("Doomfist");
return user;
}
public Class<?> getObjectType() {
return User.class;
}
}
<!-- 通过FactoryBean 实例化 bean -->
<bean id="user-by-factory-bean" class="org.geekbang.bean.instantiation.UserFactoryBean"/>
从容器获取 bean 时,当 Bean 实现了 FactoryBean 接口,会自动调用其重写的getObject()方法创建实例,并将其作为 bean 返回。如果未实现 FactoryBean 接口,自然就会调用构造方法,创建实例并返回
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-creation-context.xml");
// xml中使用class配置FactoryBean接口的实现类
User user3 = applicationContext.getBean("user-by-factory-bean", User.class);
System.out.println("通过实现FactoryBean接口创建bean: " + user3);
}
输出结果:
通过实现FactoryBean接口创建bean: User{id=6, name='Doomfist'}
前面是获取工厂生产的 bean,如果要获取工厂本身,可以使用 applicationContext.getBean("&user-by-factory-bean")获取
2. 使用注解 @Component 将 bean 注册到容器,代替 xml 配置
@Component("student")
public class StudentFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
Student student = new Student();
student.setId(2);
student.setName("小明");
return student;
}
public Class<?> getObjectType() {
return Student.class;
}
}
// 注解@Component配置FactoryBean接口的实现类
AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext("org.geekbang.bean.instantiation");
Student student = annotationContext.getBean("student", Student.class);
System.out.println("通过@Component + 实现FactoryBean接口创建bean:"+student);
输出结果
通过@Component + 实现FactoryBean接口创建bean:Student{id=2, name='小明'}
3. 使用 java api 的方式将 bean 注册到容器
// 补充,参考 4.4 章节 java api 注册 bean 的方式
2. 3 种非常规方式实例化 Bean
-
通过 ServiceLoaderFactoryBean 实例化 bean
5.1 首先来看一下
java.util.ServiceLoader的使用,ServiceLoader 会加载META-INF/services/下配置的 java 类,文件名为接口,内容为实现类:
META-INF\services\org.geekbang.bean.instantiation.UserFactory
org.geekbang.bean.instantiation.DefaultUserFactory
public class DefaultUserFactory implements UserFactory{
@Override
public User createUser() {
User user = new User();
user.setId(5L);
user.setName("Echo");
return user;
}
}
使用ServiceLoader.load(UserFactory.class)加载 DefaultUserFactory 类并创建实例,调用createUser()方法创建 User 实例并输出
public static void serviceLoaderDemo() {
// 使用ServiceLoader加载 META-INF/services/ 下配置的 UserFactory 实现类
ServiceLoader<UserFactory> serviceLoader = ServiceLoader.load(UserFactory.class);
// 遍历加载到的 UserFactory 实现类
for (UserFactory userFactory : serviceLoader) {
System.out.println("ServiceLoader加载的类:" + userFactory.createUser());
}
}
输出结果:
ServiceLoader加载的类:User{id=5, name='Echo'}
5.2 再来看一下 ServiceLoaderFactoryBean,是 FactoryBean 接口的实现类,用于实例化 ServiceLoader,即和第 4 种实例化 bean 的方式一致,不过这个 FactoryBean 接口的实现类是由 Spring 提供,实现了getObject()和getObjectType()方法,源码如下所示:
ServiceLoaderFactoryBean.java
public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {
// 继承自AbstractFactoryBean
// 创建 bean
@Override
public final T getObject() throws Exception {
// 单例
if (isSingleton()) {
return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
}
else {
// 非单例, 创建ServiceLoader实例
return createInstance();
}
}
// 使用ServiceLoader加载指定类, 返回ServiceLoader
// 继承自AbstractServiceLoaderBasedFactoryBean
@Override
protected Object createInstance() {
Assert.notNull(getServiceType(), "Property 'serviceType' is required");
// 通过ServiceLoader加载 /META-INF/services 下配置的类
// 这里使用beanClassLoader类加载器, 会加载到容器
return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader));
}
@Override
protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
return serviceLoader;
}
// 返回serviceType, 即加载的类的类型, 在xml中配置, 这里是UserFactory
public Class<?> getServiceType() {
return this.serviceType;
}
// 实现FactoryBean接口
@Override
public Class<?> getObjectType() {
return ServiceLoader.class;
}
}
5.3 使用 ServiceLoaderFactoryBean 加载META-INF/services/下配置的 java 类
配置需要加载并实例化的 Java 类到下面文件,文件名为接口,内容为实现类
META-INF\services\org.geekbang.bean.instantiation.UserFactory
org.geekbang.bean.instantiation.DefaultUserFactory
xml 中配置 ServiceLoaderFactoryBean,会将该 ServiceLoader 注册到容器并实例化,在实例化过程中会
<!-- 配置 ServiceLoaderFactoryBean -->
<bean id="userFacotoryServiceLoader" class="org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean">
<property name="serviceType" value="org.geekbang.bean.instantiation.UserFactory"/>
</bean>
从容器中获取配置的 ServiceLoade bean,然后遍历 ServiceLoader,得到DefaultUserFactory实例,调用createUser()方法创建 User 实例并输出
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean-instantiation-special-context.xml");
// xml中配置了 bean ServiceLoaderFactoryBean, 获取 ServiceLoader 实例
ServiceLoader<UserFactory> serviceLoader = beanFactory.getBean("userFacotoryServiceLoader", ServiceLoader.class);
// 遍历 ServiceLoader 实例
for (UserFactory userFactory : serviceLoader) {
System.out.println("ServiceLoaderFactoryBean加载实例化的类:" + userFactory);
System.out.println("使用bean工厂UserFactory创建user:" + userFactory.createUser());
}
输出结果:
ServiceLoaderFactoryBean加载实例化的类:org.geekbang.bean.instantiation.DefaultUserFactory@662ac478
使用bean工厂UserFactory创建user:User{id=5, name='Echo'}
// 补充:这个真的算是实例化bean吗?user 并没有被注册到spring容器,只有 userFacotoryServiceLoader bean 被注册到 spring 容器,这里的例子可能并不合适,应该配置 serviceType 为 User?
- 通过 AutowireCapableBeanFactory 实例化 Bean
// 2. 通过 AutowireCapableBeanFactory 实例化 bean
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
User user = autowireCapableBeanFactory.createBean(User.class);
System.out.println(user);
UserFactory userFactory = autowireCapableBeanFactory.createBean(DefaultUserFactory.class);
System.out.println(userFactory.createUser());
输出结果:
User{id=null, name='null'}
User{id=5, name='Echo'}
-
通过 BeanDefinitionRegistry 实例化 Bean
// 补充: 参考 4.4.2 注册Bean的时候也会实例化Bean
4.6 初始化 Bean
Bean 初始化 Initialization 后设置回调方法的三种方式:
- @PostConstruct 标记方法初始化后调用
- 实现 InitializingBean 接口的 afterPropertiesSet() 方法
- 自定义初始化方法,都是将初始化方法名保存到 BeanDefinition 元信息的属性 initMethodName 中
- xml 配置:
<bean init-method="xxx" /> - Java 注解:
@Bean(initMethod="xxx") - Java API:
AbstractBeanDefinition#setInitMethodName(String)
- xml 配置:
**调用时机:**在应用上下文启动创建 bean,构造方法调用完成后回调
- 通过 @PostConstruct 标记初始化方法,实现 InitializingBean 接口设置初始化方法
/**
* 设置 bean 初始化方法
* 1. @PostConstruct 标记方法
* 2. 实现InitializingBean#afterPropertiesSet方法
* 3. 使用@Bean(initMethod="")指定初始化方法
*/
public class Teacher implements InitializingBean {
// @PostConstruct 标记初始化方法
@PostConstruct
public void init() {
System.out.println("@PostConstruct设置初始化方法: Teacher 初始化中...");
}
// 实现InitializingBean#afterPropertiesSet方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("实现InitializingBean接口设置初始化方法: Teacher 初始化中...");
}
// 使用@Bean(initMethod="")标记该方法为初始化方法
public void initTeacher() {
System.out.println("initMethod设置初始化方法: Teacher 初始化中...");
}
}
注册 bean 到容器,并使用 @Bean(initMethod = "xxx") 指定初始化方法,容器会在启动时创建 bean,并调用初始化方法。
public class BeanInitializationDemo {
public static void main(String[] args) {
// 1.创建并启动 ApplicationContext 容器, 使用注解配置
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInitializationDemo.class);
// 2.依赖查找, 根据类型
Teacher teacher = applicationContext.getBean(Teacher.class);
}
// 指定初始化方法, 与xml方式作用一直
@Bean(initMethod = "initTeacher")
public Teacher createTeacher() {
return new Teacher();
}
}
上面使用注解的方式注册 bean 到容器,这里再补充一下 xml 方式
<bean id="teacher" class="com...Teacher" init-method="initTeacher" />
输出结果:
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
initMethod设置初始化方法: Teacher 初始化中...
通过输出结果我们也可以知道,三种初始化方法的调用顺序是:
@PostConstruct → InitializingBean接口 → initMethod,会在构造方法调用完成后调用。
- 通过 Java Api 的方式指定初始化方法,
BeanDefinitionBuilder.setInitMethodName("initTeacher")底层是AbstractBeanDefinition#setInitMethodName(String)。需要注意的是,自定义初始化的 3 种方式,都是将初始化方法名保存到 BeanDefinition 元信息的属性 initMethodName 中
public static void main(String[] args) {
// 1.创建并启动 ApplicationContext 容器, 使用注解配置
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanInitializationDemo2.class);
// 2. 创建BeanDefinition元信息, 并设置initMethod
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Teacher.class)
.setInitMethodName("initTeacher")
.getBeanDefinition();
// 3. 注册bean到容器, 设置bean名称为Winston
applicationContext.registerBeanDefinition("Winston", beanDefinition);
// 4. 查找bean, 此时会创建bean实例并调用初始化方法
applicationContext.getBean("Winston");
}
输出结果:
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
initMethod设置初始化方法: Teacher 初始化中... # 这一步是通过java api指定
实例化 Bean 与初始化 Bean 的区别?
源码分析,在应用上下文启动 refresh() 的源码中,这行代码是实例化非延迟加载的 bean的。
finishBeanFactoryInitialization(beanFactory);
4.7 延迟初始化 Bean
@Lazy 注解的主要作用主要是减少 SpringIOC 容器启动的加载时间。当出现循环依赖时,也可以添加@Lazy 解决。
Bean 延迟初始化 Lazy-Initialization 有两种方式:
- xml 配置:
<bean lazy-init="true" /> - Java 注解:
@Lazy
需要注意,延迟初始化并不是延迟注册,无论是否设置了延迟初始化,都会在应用上下文启动时注册 BeanDefinition 元信息到容器。
- Java 注解的方式设置 Bean 的延迟加载
public class BeanLazyInitializationDemo {
public static void main(String[] args) {
// 1.创建并启动应用上下文, 使用注解配置, 不需要手动refresh启动
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanLazyInitializationDemo.class);
// 非延迟加载会在应用上下文启动时初始化bean
System.out.println("========Spring应用上下文已启动=======");
// 延迟加载会在获取bean时初始化bean, 但已经注册BeanDefinition到容器了
applicationContext.getBean(Teacher.class);
}
@Lazy // 标记延迟加载,可以用在类,方法等
@Bean
public Teacher createTeacher() {
return new Teacher();
}
}
输出结果:
========Spring应用上下文已启动=======
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
注释掉 @Lazy 输出结果:
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
========Spring应用上下文已启动=======
- xml 方式设置 bean 延迟加载
<bean id="teacher" class="com...Teacher" lazy-init="true" />
4.8 销毁 Bean
所谓的销毁 Bean 就是将 Bean 从容器中移除,并不是 GC,这点需要注意。
设置 Bean 在销毁 Destroy 前回调方法的三种方式:
- @PreDestroy 标记方法
- 实现 DisposableBean 接口的 destroy() 方法
- 自定义销毁方法
- xml 配置:
<bean destroy="xxx" /> - java 注解:
@Bean(destroy="xxx") - java api:
AbstractBeanDefinition#setInitMethodName(String)
- xml 配置:
可以看到,这些方式都与 4.7 初始化 Bean 一一对应。
调用时机: 应用上下文关闭之前,
- 通过 @PreDestroy 标记销毁回调方法,实现 DisposableBean 接口设置销毁回调方法
public class Teacher implements InitializingBean, DisposableBean {
public Teacher() {
System.out.println("Teacher 构造函数....");
}
// 省略初始化后回调方法
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy设置销毁方法: Teacher 销毁中...");
}
@Override
public void destroy() throws Exception {
System.out.println("实现DisposableBean接口设置销毁方法: Teacher 销毁中...");
}
// 使用@Bean(destroyMethod="")标记该方法在销毁前调用
public void destroyTeacher() {
System.out.println("destroyMethod设置销毁方法: Teacher 销毁中...");
}
}
public class BeanDestroyDemo {
public static void main(String[] args) {
// 1.创建注解配置的应用上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanDestroyDemo.class);
System.out.println("=========Spring应用上下文开始启动=========");
// 2. 启动应用上下文
applicationContext.refresh();
System.out.println("=========Spring应用上下文已启动=========");
// 3.依赖查找, 根据类型
applicationContext.getBean(Teacher.class);
// 4. 关闭应用上下文
System.out.println("=========Spring应用上下文开始关闭=========");
applicationContext.close();
System.out.println("=========Spring应用上下文已关闭=========");
}
// 指定初始化方法, 与xml方式作用一直
@Bean(destroyMethod = "destroyTeacher")
public Teacher createTeacher() {
return new Teacher();
}
}
输出结果:
=========Spring应用上下文开始启动=========
Teacher 构造函数....
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
=========Spring应用上下文已启动=========
=========Spring应用上下文开始关闭=========
@PreDestroy设置销毁方法: Teacher 销毁中...
实现DisposableBean接口设置销毁方法: Teacher 销毁中...
destroyMethod设置销毁方法: Teacher 销毁中...
=========Spring应用上下文已关闭=========
通过输出结果我们也可以知道,三种初始化方法的调用顺序是:@PreDestroy,DisposableBean接口,destroyMethod,会在 bean 销毁前调用。
源码分析,在应用上下文关闭 close() 的源码中,最终会调用下面这行代码来销毁 Bean 并回调方法。
AbstractApplicationContext.java
protected void destroyBeans() {
getBeanFactory().destroySingletons();
}
DefaultSingletonBeanRegistry.java
public void destroySingleton(String beanName) {
removeSingleton(beanName);
DisposableBean disposableBean;
// 将实现了DisposableBean接口的beanName删除
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
// 回调实现的DisposableBean#destroy 方法
destroyBean(beanName, disposableBean);
}
- 通过 xml 方式或 java api 方式设置销毁前回调方法,参考 4.6 章节,与之对应
<bean id="teacher" class="com...Teacher" init-method="destroyTeacher" />
public static void main(String[] args) {
// 1.创建并启动 ApplicationContext 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanDestroyDemo2.class);
// 2. 创建 BeanDefinition 元信息, 并设置 DestroyMethod
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Teacher.class)
.setDestroyMethodName("destroyTeacher")
.getBeanDefinition();
// 3. 注册 bean 到容器, 设置 bean 名称为Winston
applicationContext.registerBeanDefinition("Winston", beanDefinition);
applicationContext.getBean("Winston");
// 4. 关闭容器,会回调DestroyMethod
applicationContext.close();
}
4.8 垃圾回收 Bean
Bean 垃圾回收 GC 的 3 个步骤:
- 关闭 Spring 容器(应用上下文)
- 执行 GC
- Spring Bean 重写的 finalize() 方法被调用
public static void main(String[] args) {
// 1.创建注解配置的应用上下文
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanDestroyDemo.class);
System.out.println("=========Spring应用上下文开始启动=========");
// 2. 启动应用上下文
applicationContext.refresh();
System.out.println("=========Spring应用上下文已启动=========");
// 3.依赖查找, 根据类型
applicationContext.getBean(Teacher.class);
// 4. 关闭应用上下文
System.out.println("=========Spring应用上下文开始关闭=========");
applicationContext.close();
System.out.println("=========Spring应用上下文已关闭=========");
// 强制进行 GC
System.gc();
}
public class Teacher implements InitializingBean, DisposableBean {
public Teacher() {
System.out.println("Teacher 构造函数....");
}
// 会在垃圾回收前被调用
@Override
protected void finalize() throws Throwable {
System.out.println("Teacher 析构函数, 开始GC....");
}
// 省略初始化和销毁的回调方法
}
输出结果:
=========Spring应用上下文开始启动=========
Teacher 构造函数....
@PostConstruct设置初始化方法: Teacher 初始化中...
实现InitializingBean接口设置初始化方法: Teacher 初始化中...
=========Spring应用上下文已启动=========
=========Spring应用上下文开始关闭=========
@PreDestroy设置销毁方法: Teacher 销毁中...
实现DisposableBean接口设置销毁方法: Teacher 销毁中...
destroyMethod设置销毁方法: Teacher 销毁中...
=========Spring应用上下文已关闭=========
Teacher 析构函数, 开始GC....
通过以上可以知道,bean 想要被垃圾回收,首先得关闭容器,触发 GC,才会被回收。n 年前初学 spring 时纳闷销毁 bean 到底是不是垃圾回收,bean 什么时候会被销毁,当时可能应该多问问。
4.9 面试题
-
如何注册一个 Spring Bean?
答:通过 BeanDefinition
-
什么是 Spring BeanDefinition?
答:回顾 4.1 BeanDefinition 元信息,BeanDefinition 就是 Bean 的描述信息,包括 id,class,scope,属性与属性值,lazy,初始化回调方法,销毁回调方法等信息,可以通过 BeanDefinitionBuilder 创建 BeanDefinition,可以将 BeanDefinition 注册到容器,所有 bean 的元信息都以 < beanName-> BeanDefinition> 的形式保存在 BeanFactory 的 beanDefinitionMap 属性中。
beanDefinitionMap 的类型是 ConcurrentHashMap,可以引到并发问题上。
-
Spring 容器是怎么管理注册 Bean 的?
答:参考后续章节,IOC 配置元信息读取和解析,依赖查找和注入,Bean 的生命周期
-
Spring 中控制Bean生命周期的三种方式
| init-method & destroy-method | @PostConstruct & @PreDestroy | InitializingBean & DisposableBean | |
|---|---|---|---|
| 执行顺序 | 最后 | 最先 | 中间 |
| 组件耦合度 | 无侵入(只在 <bean> 和 @Bean 中使用) | 与 JSR 规范耦合 | 与 SpringFramework 耦合 |
| 容器支持 | xml 、注解原生支持 | 注解原生支持,xml需开启注解驱动 | xml 、注解原生支持 |
| 单实例Bean | √ | √ | √ |
| 原型Bean | 只支持 init-method | √ | √ |