使用BeanDefinition来构建Spring Bean并注入Spring上下文中

在做中间件产品的时候,为了给业务方一个好用的客户端,我们一般会提供一个自定义的xxx-spring-boot-starter,那么我们就可能涉及到将自己的客户端中的某个类初始化并注入到Spring上下文中去。为了更标准化去初始化这个类,让Spring来管理我们这个对象的生命周期,那么我们经常会使用BeanDefinition来定义并通过自定义的xxxFactoryBean来真正初始化我们的对象。

先看下使用示例:

private void resolveRegistryClientBeanDefinition(BeanDefinitionRegistry registry, DispatchProperty dispatchProperty) {
        String beanName = Client.class.getName();
        ClientBeanDefinitionBuilder beanDefinitionBuilder = new ClientBeanDefinitionBuilder();
        beanDefinitionBuilder.property(dispatchProperty);
        BeanDefinition beanDefinition = beanDefinitionBuilder.build();
        if (!context.containsBean(beanName)) {
            registry.registerBeanDefinition(beanName, beanDefinition);
            LOGGER.info("NBP-CLIENT-STARTER", "registered beanDefinition of {} in spring context.", beanName);
        } else {
            LOGGER.warn("NBP-CLIENT-STARTER", "beanDefinition of {} has already registered in spring context.", beanName);
        }
    }

以上,我们使用ClientBeanDefinitionBuilder来定义如何构建ClientFactoryBean,然后通过ClientFactoryBean来创建Client对象,并通过registry.registerBeanDefinition(beanName, beanDefinition); 来将其注入到Spring上下文中去。

我们再来看下ClientBeanDefinitionBuilder和ClientFactoryBean里面是如何实现的。

ClientBeanDefinitionBuilder

package com.xxx.arch.mw.nbp.client.spring;

import com.xxx.arch.mw.nbp.client.configuration.DispatchProperty;
import com.xxx.arch.mw.nbp.client.constant.ClientConstants;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;

/**
 * @created 2022-11-30 3:08 PM
 * @description:
 */
public class ClientBeanDefinitionBuilder {

    private DispatchProperty property;

    ClientBeanDefinitionBuilder() {
    }

    ClientBeanDefinitionBuilder property(DispatchProperty property) {
        this.property = property;
        return this;
    }

    BeanDefinition build() {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ClientFactoryBean.class);
        builder.addPropertyValue("property", this.property);
        builder.addPropertyReference("publisher", ClientConstants.NBP_PUBLISHER_IMPL_BEAN_NAME);
        builder.addPropertyReference("subscriber", ClientConstants.NBP_SUBSCRIBER_IMPL_BEAN_NAME);
        builder.addDependsOn(ClientConstants.NBP_PUBLISHER_IMPL_BEAN_NAME);
        builder.addDependsOn(ClientConstants.NBP_SUBSCRIBER_IMPL_BEAN_NAME);
        builder.setInitMethodName(ClientConstants.INIT_METHOD);

        return builder.getBeanDefinition();
    }

}

ClientFactoryBean

package com.xxx.arch.mw.nbp.client.spring;

import com.xxx.arch.mw.nbp.client.Client;
import com.xxx.arch.mw.nbp.client.DefaultClient;
import com.xxx.arch.mw.nbp.client.configuration.DispatchProperty;
import com.xxx.arch.mw.nbp.client.publish.Publisher;
import com.xxx.arch.mw.nbp.client.subscribe.Subscriber;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;

/**
 * @created 2022-11-30 3:32 PM
 * @description:
 */
public class ClientFactoryBean implements FactoryBean, EnvironmentAware, InitializingBean {
    private ConfigurableEnvironment environment;

    private DispatchProperty property;

    private Client client;

    private Publisher publisher;
    private Subscriber subscriber;

    public ClientFactoryBean() {
    }

    @Override
    public Object getObject() throws Exception {
        return client;
    }

    public void start() throws Exception {
        if (client == null) {
            client = new DefaultClient(this.property);
            if (publisher != null) {
                ((DefaultClient) client).setPublisher(publisher);
            }
            if (subscriber != null) {
                ((DefaultClient) client).setSubscriber(subscriber);
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    }

    @Override
    public Class getObjectType() {
        return Client.class;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public DispatchProperty getProperty() {
        return property;
    }

    public void setProperty(DispatchProperty property) {
        this.property = property;
    }

    public Publisher getPublisher() {
        return publisher;
    }

    public void setPublisher(Publisher publisher) {
        this.publisher = publisher;
    }

    public Subscriber getSubscriber() {
        return subscriber;
    }

    public void setSubscriber(Subscriber subscriber) {
        this.subscriber = subscriber;
    }
}

我们看到Client内部还分别依赖了Publisher和Subscriber,此实现也类似,详见如下:

如何构建Publisher

 private void resolveRegistryPublisherImplBeanDefinition(ConfigurableListableBeanFactory beanFactory,
                                                            BeanDefinitionRegistry registry,
                                                            DispatchProperty dispatchProperty) {
        String beanName = ClientConstants.NBP_PUBLISHER_IMPL_BEAN_NAME;
        // 额外增加主子任务需要发布者订阅的模板
        PUBLISHER_TEMPLATE_SET.addAll(MAPREDUCE_PUBLISHER_TEMPLATE_SET);
        BeanDefinition beanDefinition = new PublisherBeanDefinitionBuilder()
                .property(dispatchProperty)
                .beanFactory(beanFactory)
                .templates(PUBLISHER_TEMPLATE_SET)
                .filters(CUSTOM_PUBLISH_FILTER_SET)
                .build();
        if (!context.containsBean(beanName)) {
            registry.registerBeanDefinition(beanName, beanDefinition);
            LOGGER.info("NBP-CLIENT-STARTER",
                    "registered beanDefinition of {} in spring context.", beanName);
        } else {
            LOGGER.warn("NBP-CLIENT-STARTER",
                    "beanDefinition of {} has already registered in spring context.", beanName);
        }
    }

PublisherBeanDefinitionBuilder

package com.xxx.arch.mw.nbp.client.spring;

import com.xxx.arch.mw.nbp.client.configuration.DispatchProperty;
import com.xxx.arch.mw.nbp.client.constant.ClientConstants;
import com.xxx.arch.mw.nbp.common.domain.Template;
import com.xxx.commons.data.domain.tuple.Pair;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @created 2022-11-30 3:08 PM
 * @description:
 */
public class PublisherBeanDefinitionBuilder {

    private DispatchProperty property;

    /**
     *  依赖的bean名称列表
     */
    private List dependsOns = new ArrayList<>();

    /**
     * 发布的任务模板列表
     */
    private Set