前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 手动创建 bean 的两种方式

Spring 手动创建 bean 的两种方式

作者头像
用户3147702
发布2022-12-21 17:41:58
1.8K0
发布2022-12-21 17:41:58
举报

1. 引言

随着我们对 spring 使用程度的日益加深,有时我们会觉得通过传统的 bean 创建方式不能满足我们的需要。例如我们需要解析一个配置来决定具体如何生成 bean,这时,手动创建 bean 就显得非常必要了。

本文我们就来介绍一下如何在 spring 启动过程中实现自己手动去创建 bean。

2. Spring 创建 bean 的流程

此前的文章中,我们已经介绍过 spring 启动过程中的切入点:

添加 SpringBoot 自定义启动代码的六种方式(上)

添加 SpringBoot 自定义启动代码的六种方式(下)

Spring 创建 bean 的流程大致分为以下几个步骤:

  1. 加载相应的 class;
  2. 创建 class 对应的 bean 描述对象 BeanDefinition 对象;
  3. 将 BeanDefinition 对象放入 BeanFactory;
  4. 加载 BeanFactoryPostProcessor 的实现类,进行 BeanFactory 后置处理;
  5. 实例化 bean;
  6. 属性赋值;
  7. 初始化,包括此前提到的 @PostConstruct 注解、调用 InitializingBean 实现类等;
  8. 调用 BeanNameAware、ApplicationContextAware 等实现类;
  9. 生成 bean;
  10. 将生成的 bean 放入 SpringContext。

通过这个流程,我们可以清晰地发现,通过实现 BeanFactoryPostProcessor,就可以创建我们自己的 bean 了。

3. BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor 接口是继承自 BeanFactoryPostProcessor 的子接口:

代码语言:javascript
复制
 /**
  * Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for
  * the registration of further bean definitions <i>before</i> regular
  * BeanFactoryPostProcessor detection kicks in. In particular,
  * BeanDefinitionRegistryPostProcessor may register further bean definitions
  * which in turn define BeanFactoryPostProcessor instances.
  *
  * @author Juergen Hoeller
  * @since 3.0.1
  * @see org.springframework.context.annotation.ConfigurationClassPostProcessor
  */
 public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
 
   /**
    * Modify the application context's internal bean definition registry after its
    * standard initialization. All regular bean definitions will have been loaded,
    * but no beans will have been instantiated yet. This allows for adding further
    * bean definitions before the next post-processing phase kicks in.
    * @param registry the bean definition registry used by the application context
    * @throws org.springframework.beans.BeansException in case of errors
    */
   void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
 
 }
 
 /**
  * Factory hook that allows for custom modification of an application context's
  * bean definitions, adapting the bean property values of the context's underlying
  * bean factory.
  *
  * <p>Useful for custom config files targeted at system administrators that
  * override bean properties configured in the application context. See
  * {@link PropertyResourceConfigurer} and its concrete implementations for
  * out-of-the-box solutions that address such configuration needs.
  *
  * <p>A {@code BeanFactoryPostProcessor} may interact with and modify bean
  * definitions, but never bean instances. Doing so may cause premature bean
  * instantiation, violating the container and causing unintended side-effects.
  * If bean instance interaction is required, consider implementing
  * {@link BeanPostProcessor} instead.
  *
  * <h3>Registration</h3>
  * <p>An {@code ApplicationContext} auto-detects {@code BeanFactoryPostProcessor}
  * beans in its bean definitions and applies them before any other beans get created.
  * A {@code BeanFactoryPostProcessor} may also be registered programmatically
  * with a {@code ConfigurableApplicationContext}.
  *
  * <h3>Ordering</h3>
  * <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
  * {@code ApplicationContext} will be ordered according to
  * {@link org.springframework.core.PriorityOrdered} and
  * {@link org.springframework.core.Ordered} semantics. In contrast,
  * {@code BeanFactoryPostProcessor} beans that are registered programmatically
  * with a {@code ConfigurableApplicationContext} will be applied in the order of
  * registration; any ordering semantics expressed through implementing the
  * {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
  * programmatically registered post-processors. Furthermore, the
  * {@link org.springframework.core.annotation.Order @Order} annotation is not
  * taken into account for {@code BeanFactoryPostProcessor} beans.
  *
  * @author Juergen Hoeller
  * @author Sam Brannen
  * @since 06.07.2003
  * @see BeanPostProcessor
  * @see PropertyResourceConfigurer
  */
 @FunctionalInterface
 public interface BeanFactoryPostProcessor {
 
   /**
    * Modify the application context's internal bean factory after its standard
    * initialization. All bean definitions will have been loaded, but no beans
    * will have been instantiated yet. This allows for overriding or adding
    * properties even to eager-initializing beans.
    * @param beanFactory the bean factory used by the application context
    * @throws org.springframework.beans.BeansException in case of errors
    */
   void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
 
 }

通过注释,我们可以看到,BeanDefinitionRegistryPostProcessor 接口的实现类是先于 BeanFactoryPostProcessor 的实现类执行的。

BeanDefinitionRegistryPostProcessor 可以回调 BeanDefinitionRegistry 参数,有了它我们就可以创建自己的 BeanDefinition 了。

而 BeanFactoryPostProcessor 则回调的是 ConfigurableListableBeanFactory 参数,通过它,我们可以跳过 BeanDefinition 的步骤,直接将自己生成的对象放到 BeanFactory 中。

4. 通过 BeanDefinition 创建 bean

有了上述基础知识,我们就可以编写代码实现自己的 bean 创建了。

首先,我们介绍一下如何通过 BeanDefinition 创建 bean。

4.1 BeanDefinition

BeanDefinition 是一个在 spring-context 包中的接口,他继承自两个接口:

  • BeanMetadataElement:该接口只有一个方法 getSource,该方法返回 Bean 的来源。
  • AttributeAccessor:该接口主要规范了问任意对象元数据的方法。

除了上述两个接口的方法,在 BeanDefinition 中,定义了一系列 Bean 属性,包括是否是单例,是否是抽象 bean,bean 的类型,配置获取该 bean 的 BeanFactory 名称等,都一一对应了我们平常配置一个 bean 时可以提供的配置,这里就不一一赘述了。

下面是 spring 预先定义好的 BeanDefinition 的实现类:

重点可以关注一下 GenericBeanDefinition,GenericBeanDefinition 是从 Spring2.5 以后新加入的 BeanDefinition 实现类。GenericBeanDefinition 可以动态设置父 Bean,同时兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能,它提供了 bean 的默认配置,是官方推荐我们使用的 BeanDefinition 实现类。

4.2 通过 BeanDefinition 创建自定义 bean

通过 BeanDefinition 创建 bean 的方式与我们平常通过 xml 的方式创建 bean 几乎没有什么差别,所有 xml 中可以配置的属性,都可以在 BeanDefinition 接口中找到对应的 set 方法。

下面是一个示例:

代码语言:javascript
复制
 private static BeanDefinition createBeanDefinition(ComponentDefinition componentDefinition) {
     GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
 
     beanDefinition.setBeanClass(componentDefinition.getClass());
     beanDefinition.setScope(getScope(componentDefinition.getScope()));
     beanDefinition.setAutowireCandidate(true);
     beanDefinition.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
     beanDefinition.setDependsOn(componentDefinition.getDependsOn());
     beanDefinition.setLazyInit(componentDefinition.isLazyInit());
 
     ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
     for (Object value : componentDefinition.getConstructorArguments()) {
         constructorArgumentValues.addIndexedArgumentValue(constructorArgumentValues.getArgumentCount(), value);
     }
     beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
 
     MutablePropertyValues propertyValues = new MutablePropertyValues();
     for (PropertyValue propertyValue : componentDefinition.getPropertyValues()) {
         propertyValues.add(propertyValue.getName(), propertyValue.getValue());
     }
     beanDefinition.setPropertyValues(propertyValues);
 
     return beanDefinition;
 }

对于 xml 定义 bean 时传递的 properties,我们只要将他们一一添加到 propertyValues 对象,并且赋值给 BeanDefinition 对象即可。

4.2 在 propertyValues 中引用其他 bean

但也许你会发现一个问题,如果 property 的值是对另一个 bean 的引用,也就是通过 ref 的方式来赋值的,那么要怎么添加到 propertyValues 对象呢?spring 提供了两种方法:

  1. 传递被引用 bean 的 BeanDefinition;
  2. 通过 RuntimeBeanReference 对象。

1. 传递被引用 bean 的 BeanDefinition

代码语言:javascript
复制
 GenericBeanDefinition bd2 = new GenericBeanDefinition();
 bd2.setBeanClass(Dependency.class);
 
 GenericBeanDefinition bd1 = new GenericBeanDefinition();
 bd1.setBeanClass(Component.class);
 
 MutablePropertyValues values = new MutablePropertyValues();
 values.addPropertyValue("dependency", bd2);
 
 bd1.setPropertyValues(values);

2. 通过 RuntimeBeanReference 对象

代码语言:javascript
复制
 values.addPropertyValue("beanProperty", new RuntimeBeanReference("beanName"));

5. 通过 BeanFactory 创建 bean

如上文所述,通过实现 BeanFactoryPostProcessor,spring 为我们回调了 ConfigurableListableBeanFactory 对象,利用 ConfigurableListableBeanFactory 对象,我们可以直接在 beanFactory 中添加 已经实例化的对象来完成 bean 的创建。

代码语言:javascript
复制
 beanFactory.registerSingleton(beanName, object);

但这有一个前提,那就是已经实例化的 object 必须不能依赖其他 bean 及配置的注入。

6. 需要注意的问题

为什么上一小节我们会提到我们自行实例化的 bean 对象不能依赖其他 bean 及配置的注入呢?

让我们回到本文的第 2 部分,你会发现一个问题,那就是在 BeanDefinitionRegistryPostProcessor 切入点的执行是在 2、3、4 步骤,而 spring 的初始化则是在第 5、6、7 步骤。因此,一旦 bean 对象在 BeanDefinitionRegistryPostProcessor 实现类中被实例化,那么,spring 在初始化阶段就不会再对它进行一次重复处理,也就无法将其他 bean 和配置注入到这个已经实例化过的 bean 中了。

这看起来很容易避免,但在实际开发过程中有时候并不那么明显。比如,如果我们使用 Spring 提供的 ApplicationContext.getBeansOfType() 方法来获取指定类型的 bean 时,spring 会尝试将所有该类型的 bean 全部实例化后返回回来。看起来,只要我们保证所有这里指定的这个类型的对象实例化时都不依赖任何其他 bean 和配置的注入,这一操作就不会存在问题,但实际上,问题是潜在的。

当我们执行 ApplicationContext.getBeansOfType() 时,Spring 回去寻找上下文中所有匹配参数类型的 bean,但如果 bean 需要被工厂方法创建,那么,Spring 有两种方法获知这个工厂方法最终会创建的 Bean 类型:

  1. 通过 BeanFactory 的 getType 方法或者 isTypeMatch 方法;
  2. 实际调用 BeanFactory 的实例化方法来获取实例化后的 bean,并比较其类型。

如果在 Spring 运行环境中,某个 BeanFactory 的实现的 getType 方法返回的是 null,那么,当我们执行 ApplicationContext.getBeansOfType() 方法时,Spring 只能选择第二种方法来让这个 BeanFactory 提前实例化。

这意味着,如果我们在 BeanDefinitionRegistryPostProcessor 的实现类中,调用 ApplicationContext.getBeansOfType(),就有可能造成潜在的我们不知道的某个 BeanFactory 提前创建并实例化它所要创建的 bean,而这个 bean 是否需要依赖其他 bean 或配置的注入,我们就更无从得知了,这是一个很大的隐患。

不过 Spring 已经考虑到了这个问题,ApplicationContext.getBeansOfType() 方法拥有重载方法,可以避免对象的提前实例化:

代码语言:javascript
复制
 <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException;

只要最后一个参数传递 false,即可避免上述问题。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. Spring 创建 bean 的流程
  • 3. BeanDefinitionRegistryPostProcessor
  • 4. 通过 BeanDefinition 创建 bean
    • 4.1 BeanDefinition
      • 4.2 通过 BeanDefinition 创建自定义 bean
        • 4.2 在 propertyValues 中引用其他 bean
          • 1. 传递被引用 bean 的 BeanDefinition
          • 2. 通过 RuntimeBeanReference 对象
      • 5. 通过 BeanFactory 创建 bean
      • 6. 需要注意的问题
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档