暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用Spring BeanDefinition手动注册Bean

编程阁楼 2021-05-23
869
一般情况下,我们Spring应用中的bean都是通过注解或者xml注入到容器中的,有些情况下我们可能想手动往容器中注入bean,即编程方式注入bean。
本文所使用源码包版本:spring-beans-5.0.7.RELEASE.



1. 如何注册?

     Spring 中用BeanDefinition接口描述一个bean,Spring容器中用Map<String, BeanDefinition> beanDefinitionMap存储beanName和BeanDefinition对象的映射关系(beanDefinitionMap 可参考DefaultListableBeanFactory)。
Spring实例化一个bean都是先从 beanDefinitionMap 中获取beanDefinition对象,进而构造出对应的bean。因此,我们手动注册bean的问题,就演化为如何往这个 beanDefinitionMap 放入我们要注册bean对应的 BeanDefinition 对象。Spring 提供了 BeanDefinitionRegistry 接口来操作底层beanFactory实现的beanDefinitionMap。
推荐通过实现 BeanDefinitionRegistryPostProcessor 接口操作 BeanDefinitionRegistry
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;


}


2. 测试代码

我们有个类 Person,把它手动添加到Spring 容器中成为一个bean:

public class Person {


private String name ;


public void test(){
System.out.println("hello world!");
}


public void setName(String name) {
this.name = name;
}
}
主程序:


@EnableAspectJAutoProxy
@ComponentScan("com.jimbean.spring.test")
public class BootStrap {


public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
// 这里也可以使用 BeanDefinitionBuilder
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(Person.class);
beanFactory.registerBeanDefinition("person", beanDefinition);
Person person = (Person) applicationContext.getBean("person");
person.test();
}
}

程序运行结果:

hello world!

以上代码从容器中获取到了名称为person
的bean,并且调用方案打印了调用结果。
另外,在类Person中,可以使用@Autowired注入其他已有的bean的,bean的依赖关系也是支持的。如下:
public class Person {


private String name ;


@Autowired
Animal animal;


public void test(){
y.test();
System.out.println("hello world!");
}


public void setName(String name) {
this.name = name;
}
}
其中Animal类定义为一个Spring bean:
@Component
public class Animal {


public Animal() {
System.out.println("Construct Animal");
}


public void test(){
System.out.println("Animal.test()...");
}


}

上述测试代码输出结果:

Construct Animal

Animal.test()...

hello world!



3. 源码解读:

BeanDefinition 接口:

        BeanDefinition描述了一个bean实例,该实例具有属性值,构造函数参数值以及具体实现所提供的更多信息。类图如下,主要三个实现类:RootBeanDefinition、ChildBeanDefinition 和 GenericBeanDefinition。Bean的属性,比如对应Java类的Class、作用域scope、是否延迟加载lazyInit等,都可以通过BeanDefinition控制。
继承结构:

        RootBeanDefinition 可以在配置阶段用于注册各个Bean定义,上面的测试用例 BeanDefinition 也可以使用 RootBeanDefinition 实现。但是官方推荐,从Spring 2.5开始,以编程方式注册Bean定义的首选方法是{@link GenericBeanDefinition}类。

BeanDefinitionRegistry 接口:
包含BeanDefinition的注册表的接口,例如 RootBeanDefinition 和 ChildBeanDefinition 实例。通常由内部使用AbstractBeanDefinition层次结构的 BeanFactory 实现。
/**
* Register a new bean definition with this registry.
* Must support RootBeanDefinition and ChildBeanDefinition.
* @param beanName the name of the bean instance to register
* @param beanDefinition definition of the bean instance to register
* @throws BeanDefinitionStoreException if the BeanDefinition is invalid
* or if there is already a BeanDefinition for the specified bean name
* (and we are not allowed to override it)
* @see RootBeanDefinition
* @see ChildBeanDefinition
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;

        注意用到的 registerBeanDefinition 方法,如果构造的 BeanDefinition 无效,或者指定的bean名称已经存在BeanDefinition(如果不允许覆盖它)会抛出 BeanDefinitionStoreException 异常。BeanDefinitionRegistry 还有其他的方法,比如根据bean名称移除beanDefinition的方法 removeDefinition(String beanName)等。

DefaultListableBeanFactory 类:
        DefaultListableBeanFactory中用Map<String,BeanDefinition> beanDefinitionMap 保存beanName和BeanDefinition对象的映射关系,同时实现了BeanDefinitionRegistry接口,用来操作beanDefinitionMap,其中 registerBeanDefinition 方法:


@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//校验传入beanDefinition的正确性
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}

BeanDefinition oldBeanDefinition;

oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//如果已存在该bean名称的beanDefinition,并且不允许覆盖,则抛异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
//检查beanFactory的bean创建阶段是否已开始,可以理解为容器是否已经初始化过了
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
//把新加入的beanName-beanDefinition映射加入beanDefinitionMap
//把beanName加入的维护的beanDefinitionName列表(ArrayList)中
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
//如果手动注册的单例bean名称包含了beanName,需要移除
//这个地方默认构造的beanDefinition对象作用域也是“”,等价于单例,但是却没有把 beanName加入到manualSingletonNames集合(LinkedHashSet)中
//最终beanName对应的bean实例化后,manualSingletonNames还是没有这个beanName,略奇怪。。。
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
//如果oldBeanDefinition不为null,且单例池缓存中已经有beanName对应的bean,则重置给定bean的所有bean定义缓存,包括从其派生的bean的缓存。
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

SingletonBeanRegistry 接口:

SingletonBeanRegister接口,是单例bean的管理接口,也可以用来注册单例bean,方法:

void registerSingleton(String beanName, Object singletonObject);
DefaultListableBeanFactory类也实现了该接口,我们可以将上面的测试代码修改下,也可以达到注册一个单例bean的目的:
@EnableAspectJAutoProxy
@ComponentScan("com.jimbean.spring.test")
public class BootStrap {

public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.registerSingleton("person", new Person());
Person person = (Person) applicationContext.getBean("person");
person.test();
}
}

代码也很简单,但是需要注意的是:

  • 1. bean依赖项不好处理

  • 2. 待注入的实例应该被完全初始化,注册表不会执行任何初始化回调方法,比如InitializingBean的{@code afterPropertiesSet}方法

文章转载自编程阁楼,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论