4-Spring Bean 基础

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
Spring基础配置主要包括以下几个方面的内容:\[1\]\[2\]\[3\] 1. Spring的类包必须已经放在Spring的类容器下面。这意味着我们需要将Spring的类包放在项目的类路径下,以便Spring容器能够正确加载和管理这些类。 2. 应用程序应当为Spring提供完备的Bean的配置信息。这些配置信息可以通过XML文件或者注解的方式进行定义,用于描述Bean的属性、依赖关系、行为配置等。 3. Bean的类都已经放在Spring的类容器下面。这意味着我们需要将所有需要由Spring管理的Bean的类放在Spring容器能够扫描到的位置,以便Spring能够正确实例化和管理这些Bean。 4. Spring的配置文件是Spring容器对Bean进行生产以及关系注入的图纸。这个配置文件是一个或多个标准的XML文档,其中最常见的是ApplicationContext.xml,它是Spring的默认配置文件。在容器启动时,如果找不到其他的配置文件,Spring会尝试加载这个默认的配置文件。 5. Bean的配置信息由Bean的元数据信息组成,包括Bean的实现类、属性信息、依赖关系、行为配置以及创建方式定义等。这些信息用于告诉Spring容器如何实例化和装配Bean,以及如何为上层应用提供准备就绪的运行环境。 综上所述,Spring基础配置包括将Spring的类包放在类路径下、提供完备的Bean的配置信息、将Bean的类放在Spring容器能够扫描到的位置、配置Spring的配置文件以及定义Bean的元数据信息。这些配置将帮助Spring容器正确加载和管理Bean,为应用程序提供准备就绪的运行环境。 #### 引用[.reference_title] - *1* *2* *3* [Spring bean配置的六种方式](https://blog.csdn.net/echizao1839/article/details/88063013)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值