spring组件之BeanDefinition

84 阅读8分钟

注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:gitee.com/funcy/sprin….

1. 什么是 BeanDefinition

BeanDefinition 从名称上来看,就是bean定义,用来定义 spring bean 的信息。

在 java 中,定义一个类的元信息(构造方法,成员变量,成员方法等),使用的是Class类,一个.class文件加载到jvm后,都会生成一个Class对象,在对象实例化时,就根据这个Class对象的信息来生成。

在 spring 中,也有这么一个类来定义 bean 的信息,这个类就是 BeanDefinition,它定义了spring bean如何生成,如何初始化,如何销毁等,我们来看看它支持的部分方法:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

    /**
     * 设置父 BeanDefinition
     * BeanDefinition 有个类似继承的概念,这里指定了父BeanDefinition
     * 实例化bean时,会合并父BeanDefinition
     */
    void setParentName(@Nullable String parentName);

    /**
     * 获取父Bean
     */
    @Nullable
    String getParentName();

    /**
     * 设置beanClass名称
     * 实例化时,会实例化成这个 Class 的对象
     */
    void setBeanClassName(@Nullable String beanClassName);

    /**
     * 获取beanClass名称
     */
    @Nullable
    String getBeanClassName();

    /**
     * 设置bean的作用范围,单例或原型
     */
    void setScope(@Nullable String scope);

    /**
     * 获取bean的作用范围,单例或原型
     */
    @Nullable
    String getScope();

    /**
     * 设置懒加载
     */
    void setLazyInit(boolean lazyInit);

    /**
     * 是否为懒加载
     */
    boolean isLazyInit();

    /**
     * 设置该Bean依赖的所有Bean
     * 即 @DependsOn 指定的bean
     */
    void setDependsOn(@Nullable String... dependsOn);

    /**
     * 返回该Bean的所有依赖。
     */
    @Nullable
    String[] getDependsOn();

    /**
     * 设置是否作为自动注入的候选对象。
     */
    void setAutowireCandidate(boolean autowireCandidate);

    /**
     * 是否作为自动注入的候选对象
     */
    boolean isAutowireCandidate();

    /**
     * 设置是否为主要的,当容器中存在多个同类型的bean时,可以只返回主要的
     * 就是 @Primary 的作用
     */
    void setPrimary(boolean primary);

    /**
     * 是否为主要的bean
     */
    boolean isPrimary();

    /**
     * 针对factoryBean
     * 指定factoryBean的名称
     */
    void setFactoryBeanName(@Nullable String factoryBeanName);

    /**
     * 针对factoryBean
     * 获取factoryBean的名称
     */
    @Nullable
    String getFactoryBeanName();

    /**
     * 设置工厂方法的名称
     * 例如 @Bean 标记的方法
     */
    void setFactoryMethodName(@Nullable String factoryMethodName);

    /**
     * 返回工厂方法的名称
     * 例如 @Bean 标记的方法
     */
    @Nullable
    String getFactoryMethodName();

    /**
     * 获取构造就去的参数值
     */
    ConstructorArgumentValues getConstructorArgumentValues();

    /**
     * 构造方法是否有参数
     */
    default boolean hasConstructorArgumentValues() {
        return !getConstructorArgumentValues().isEmpty();
    }

    /**
     * 获取属性值
     * 这里可以自主设置值作为构造方法、工厂方法的参数
     */
    MutablePropertyValues getPropertyValues();

    /**
     * 是否有属性值
     */
    default boolean hasPropertyValues() {
        return !getPropertyValues().isEmpty();
    }

    /**
     * 设置初始化方法名称
     */
    void setInitMethodName(@Nullable String initMethodName);

    /**
     * 获取初始化方法名称
     */
    @Nullable
    String getInitMethodName();

    /**
     * 设置销毁方法名称
     */
    void setDestroyMethodName(@Nullable String destroyMethodName);

    /**
     * 获取销毁方法名称
     */
    @Nullable
    String getDestroyMethodName();

    /**
     * 是否为单例bean
     */
    boolean isSingleton();

    /**
     * 是否为原型bean
     */
    boolean isPrototype();

    /**
     * 如果这个 Bean 是被设置为 abstract,那么不能实例化,常用于作为 父bean 用于继承
     */
    boolean isAbstract();

    ...

}

可以看到,BeanDefinition支持的方法非常多,有很多是我们平时使用时指定的:

  • setScope(...):设置bean的作用范围,由@Scope指定
  • setLazyInit(...):设置懒加载,由@Lazy指定
  • setDependsOn(...):设置bean依赖,由@DependsOn指定
  • setPrimary(...):设置为主要bean,由@Primary指定
  • setFactoryMethodName(...):设置工厂方法名称,例如由@Bean标记的方法

以上是在注解时代可以指定的,还有些是在xml时代指定的,如:

  • setInitMethodName(...):设置初始化方法 ,由init-method指定
  • setDestroyMethodName(...):设置销毁方法,由destroy-method指定

2. spring 提供了哪些BeanDefinition

BeanDefinition 是一个接口,我们当然不能直接使用,接下来我们来看看Spring提供了哪些BeanDefinition

spring提供的BeanDefinition基本就是上图所示的几种了,这里我们主要看这向种:

  • RootBeanDefinition
  • ChildBeanDefinition
  • GenericBeanDefinition
  • ScannedGenericBeanDefinition
  • AnnotatedGenericBeanDefinition

2.1 RootBeanDefinitionChildBeanDefinition

在前面提到了BeanDefinition继子的概念,这里就是用来处理继承的,一般来说,我们可以在RootBeanDefinition定义公共参数,然后在ChildBeanDefinition中定义各自的内容,示例如下:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    // RootBeanDefinition
    RootBeanDefinition root = new RootBeanDefinition();
    root.setBeanClass(User.class);
    root.getPropertyValues().add("name", "123");
    // 这种注册方法仅做展示,实际项目中不建议使用
    // 使用项目建议使用的 BeanDefinitionRegistryPostProcessor 提供的方法
    context.registerBeanDefinition("root", root);

    // ChildBeanDefinition
    ChildBeanDefinition child1 = new ChildBeanDefinition("root");
    child1.getPropertyValues().add("age", "11");
    // 这种注册方法仅做展示,实际项目中不建议使用
    // 使用项目建议使用的 BeanDefinitionRegistryPostProcessor 提供的方法
    context.registerBeanDefinition("child1", child1);

    // ChildBeanDefinition
    ChildBeanDefinition child2 = new ChildBeanDefinition("root");
    child2.getPropertyValues().add("age", "12");
    // 这种注册方法仅做展示,实际项目中不建议使用
    // 使用项目建议使用的 BeanDefinitionRegistryPostProcessor 提供的方法
    context.registerBeanDefinition("child2", child2);
    // 启动容器
    context.refresh();

    User rootUser = (User) context.getBean("root");
    User child1User = (User) context.getBean("child1");
    User child2User = (User) context.getBean("child2");
    System.out.println(rootUser);
    System.out.println(child1User);
    System.out.println(child2User);
}

运行结果:

User{name='123', age=null}
User{name='123', age=11}
User{name='123', age=12}

可以看到,child1child1进行成功地从RootBeanDefinition继承到了属性。

2.2 GenericBeanDefinition

这是个通用的BeanDefinition,直接继承了AbstractBeanDefinition,它自身提供的方法如下:

可以看到,它自身提供的方法并不多,其操作基本继承AbstractBeanDefinition,一般情况下,我们要生成自己的BeanDefinition 时,只需要使用这个类就可以了,这里也提供一个示例:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

    GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();
    userBeanDefinition.setBeanClass(User.class);
    userBeanDefinition.getPropertyValues().add("name", "123");
    userBeanDefinition.getPropertyValues().add("age", "11");
    // 这种注册方法仅做展示,实际项目中不建议使用
    // 使用项目建议使用的 BeanDefinitionRegistryPostProcessor 提供的方法
    context.registerBeanDefinition("user", userBeanDefinition);

    // 启动容器
    context.refresh();

    User user = (User) context.getBean("user");
    System.out.println(user);
}

2.3 ScannedGenericBeanDefinition

ScannedGenericBeanDefinition 继承了 GenericBeanDefinition,同时也实现了 AnnotatedBeanDefinition接口,本身提供的方法并不多:

其操作基本来自GenericBeanDefinition,这里就不提供示例了。

2.4 AnnotatedGenericBeanDefinition

AnnotatedGenericBeanDefinition 继承了 GenericBeanDefinition,同时也实现了 AnnotatedBeanDefinition接口,本身提供的方法并不多:

其操作基本来自GenericBeanDefinition,这里就不提供示例了。

3. 操作spring容器中已有的BeanDefinition

上面的例子都是往spring容器中添加BeanDefinition,我们要如何操作spring容器中已有的BeanDefinition呢?

3.1 demo 准备

这里首先准备一个demo:

首先准备两个service

@Service
public class Service01 {

    private String name;

    public void hello() {
        System.out.println("hello " + name + ", from service01");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Service
public class Service02 {

    private String name;

    public void hello() {
        System.out.println("hello " + name + ", from service02");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

接着是主要类:

@ComponentScan
public class Demo02Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext();
        context.register(Demo02Main.class);
        context.refresh();

        Service01 service01 = (Service01) context.getBean("service01");
        Service02 service02 = (Service02) context.getBean("service02");
        service01.hello();
        service02.hello();

    }
}

运行 ,结果如下:

hello null, from service01
hello null, from service02

这说明我们的容器已经启动成功了,service01service02也初始化成功了。

3.2 不成功的尝试

这里我们反推下,service01service02已经初始化成功了,就说明容器中必然service01service02对应的beanDefifnition,我们想操作这个beanDefifnition,就必须要先获取这个beanDefifnition

如何获取spring中已经存在的beanDefifnition呢?参考第2节的示例,如果你认为在context.refresh()前获取,像这样:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Demo02Main.class);
    // 在这里获取 beanDefinition,会报错
    BeanDefinition service01Bd = context.getBeanDefinition("service01");
    service01Bd.getPropertyValues().addPropertyValue("name", "123");

    context.refresh();

    Service01 service01 = (Service01) context.getBean("service01");
    Service02 service02 = (Service02) context.getBean("service02");
    service01.hello();
    service02.hello();
}

运行,发现会报错:

Exception in thread "main" org.springframework.beans.factory
    .NoSuchBeanDefinitionException: No bean named 'service01' available

聪明如你,一定会想到,在context.refresh()前获取会报错,那在之后呢?代码像这样:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Demo02Main.class);
    context.refresh();

    // 获取 beanDefinition,修改不起作用
    BeanDefinition service01Bd = context.getBeanDefinition("service01");
    service01Bd.getPropertyValues().addPropertyValue("name", "123");

    Service01 service01 = (Service01) context.getBean("service01");
    Service02 service02 = (Service02) context.getBean("service02");
    service01.hello();
    service02.hello();

}

运行,结果如下:

确实没有报错,但是我们的修改也没起作用。在代码里,我们给service01name属性指定值为123,运行结果还是null,没起作用的原因是service01是在context.refresh()进行初始化 的,后面再怎么对它的BeanDefinition修改,也体现不到它身上。

那么究竟要怎么做呢?

3.2 BeanDefinitionRegistryPostProcessor:定制化 beanDefinition

接下来我们要放出大招了,这个大招就是:BeanFactoryPostProcessor,关于这个的介绍,可以它的介绍,可以参考spring组件之 BeanFactoryPostProcessor,这里直接给下结论:

BeanFactoryPostProcessor 中文名叫 spring beanFactory 的后置处理器,可以用来定制化 beanFactory 的一些行为。spring 为我们提供了两种 BeanFactoryPostProcessor

  • BeanFactoryPostProcessor:定制化beanFactory的行为
  • BeanDefinitionRegistryPostProcessor:定制化 beanDefinition 的行为

很明显,我们应该使用BeanDefinitionRegistryPostProcessor,直接实现这个接口:

@Component
public class MyBeanDefinitionRegistryPostProcessor 
        implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 
            throws BeansException {
        BeanDefinition service01Bd = registry.getBeanDefinition("service01");
        service01Bd.getPropertyValues().addPropertyValue("name", "123");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
            throws BeansException {
        // BeanDefinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的子接口
        // postProcessBeanFactory(...) 来自 BeanFactoryPostProcessor,我们这里不处理
    }
}

main方法跟最初保持一致:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Demo02Main.class);
    context.refresh();

    Service01 service01 = (Service01) context.getBean("service01");
    Service02 service02 = (Service02) context.getBean("service02");
    service01.hello();
    service02.hello();

}

运行,结果如下:

hello 123, from service01
hello null, from service02

可以看到 service01name确实变成123了。

实际上,BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry主要就是使用BeanDefinitionRegistry来完成BeanDefinition的操作,它支持的方法如下:

重要操作如下:

  • getBeanDefinition(...):获取BeanDefinition,存在则返回,不存在则报错,得到BeanDefinition后,就可以对其进行各种操作了
  • registerBeanDefinition(...):注册BeanDefinition,可以自定义BeanDefinition对象,然后调用该方法注册到容器中,前面的例子中,我们是在context.refresh()前调用context.registerBeanDefinition,这里需要说的是忘了前面的方法吧,并不是所有的情况下,都能拿到context.refresh()context(例如 springboot 中),因此推荐使用BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 方法注册BeanDefinition
  • removeBeanDefinition(...):移除BeanDefinition,一般情况下应该不会用到
  • containsBeanDefinition(...):判断是否包含某个BeanDefinition

4. 总结

本文主要介绍了BeanDefinition的作用:

  1. BeanDefinition的方法
  2. 几个类型的BeanDefinition的使用
  3. 使用 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 操作BeanDefinition,主要是注册、修改BeanDefinition

本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录