之前有写过一篇文章【设计模式】基于SpringBoot实现的策略模式 ,通过策略模式可以实现对于不同的参数,调用不同的bean,实现对于代码的最少侵入。基于这种方式,可以通过读取配置文件,注入到bean中,实现多个bean的配置方法。但是这种方式存在的问题就是:

  1. 配置文件每增加一个配置,需要增加一个bean配置类。
  2. 多个配置增加的时候容易遗漏,容易出错。

因此,本文将针对这种情况,基于BeanDefinitionRegistryPostProcessor实现配置文件读取动态注入多个Bean。

一、BeanDefinitionRegistryPostProcessor简介

BeanDefinitionRegistryPostProcessor即实现postProcessBeanDefinitionRegistry方法,可以修改增加BeanDefinition。

此特性可以用来动态生成bean,比如读取某个配置项,然后根据配置项动态生成bean。

spring中的容器其实就是 Beanfactory 中的一个 Map,key 是 Bean 的名称,value 是 Bean 对应的 BeanDefinition,这个注册 Bean 的方法由 BeanFactory 子类实现。

BeanDefinition 用于保存 Bean 的相关信息,包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等,它是实例化 Bean 的原材料,Spring 就是根据 BeanDefinition 中的信息实例化 Bean。

二、实现方式

首先是需要自定义实现类,实现BeanDefinitionRegistryPostProcessor以及EnvironmentAware。本文中还实现了ApplicationContextAware 接口,为的是重新获取已经注册的bean,如果没有这个需求是可以不实现的。

ApplicationContextAware 获取的对象是 ApplicationContext。 其继承了 Aware 接口,并定义一个 set 方法,参数就是 ApplicationContext 对象。 

1. 核心代码实现:

/**
* RestService根据配置自动注入Bean
*
* @author: xxx
* @date: 2020-09-11 9:28
* ApplicationContextAware 是为了重新获取bean而实现,不用可以不添加,后续会说明
*/
@Component
@Slf4j
public class RestServiceBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ApplicationContextAware {


    private Environment environment;

    private ApplicationContext applicationContext;



    //配置文件前缀
    private static final String CORE_URL_PREFIX = "core";
    //三种rest service前缀对应配置文件的不同地址
    private static final String CORE_RESTBEAN_POSTFIX = "CoreSponsorRestService";
    private static final String AUTH_RESTBEAN_POSTFIX = "ApiAuthRestService";
    private static final String OP_RESTBEAN_POSTFIX = "OpRestSponsorService";


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        try {
                        //获取配置文件配置,并绑定
            BindResult<MonitorRestBeanConfig> restServiceBindResult = Binder.get(environment).bind(CORE_URL_PREFIX, MonitorRestBeanConfig.class);
            MonitorRestBeanConfig monitorRestBeanConfig = restServiceBindResult.get();
            Map<String, MonitorUrlConfig> coreRestServices = monitorRestBeanConfig.getUrls();

            //将指定类型的service放入map中value,key使用对应的pool-name。方便后期使用的时候通过poolName进行查找bean。
            Map<String, CoreSponsorRestService> coreRestMaps = new ConcurrentHashMap<>();
            Map<String, OpRestSponsorService> opRestMaps = new ConcurrentHashMap<>();
            Map<String, ApiAuthRestService> authRestMaps = new ConcurrentHashMap<>();

            //遍历所有的配置
            for (Map.Entry<String, monitorUrlConfig> entry : coreRestServices.entrySet()) {
                String poolId = entry.getKey();
                MonitorUrlConfig monitorUrlConfig = entry.getValue();
                String beanName = monitorUrlConfig.getBeanName();
                String coreUrl = monitorUrlConfig.getCoreUrl();
                String opUrl = monitorUrlConfig.getOpUrl();

                //创建bean
                BeanDefinitionBuilder coreRestBean = BeanDefinitionBuilder.rootBeanDefinition(CoreSponsorRestService.class);
                //向bean中baseUrl参数中注入配置文件的配置内容
                coreRestBean.addPropertyValue("baseUrl", coreUrl);
                //注册beandefinition,并定义好bean的名称
                beanDefinitionRegistry.registerBeanDefinition(beanName + CORE_RESTBEAN_POSTFIX, coreRestBean.getBeanDefinition());
                //获取到环境中已注册的bean并放入map中
                coreRestMaps.put(poolId, (CoreSponsorRestService) applicationContext.getBean(beanName + CORE_RESTBEAN_POSTFIX));


                BeanDefinitionBuilder authRestBean = BeanDefinitionBuilder.rootBeanDefinition(ApiAuthRestService.class);
                authRestBean.addPropertyValue("baseUrl", coreUrl);
                beanDefinitionRegistry.registerBeanDefinition(beanName + AUTH_RESTBEAN_POSTFIX, authRestBean.getBeanDefinition());
                authRestBean.put(poolId, (ApiAuthRestService) applicationContext.getBean(beanName + AUTH_RESTBEAN_POSTFIX));


                BeanDefinitionBuilder opRestBean = BeanDefinitionBuilder.rootBeanDefinition(OpRestSponsorService.class);
                opRestBean.addPropertyValue("baseUrl", opUrl);
                beanDefinitionRegistry.registerBeanDefinition(beanName + OP_RESTBEAN_POSTFIX, opRestBean.getBeanDefinition());
                opRestMaps.put(poolId, (OpRestSponsorService) applicationContext.getBean(beanName + OP_RESTBEAN_POSTFIX));
            }

            //将上文中的map注册为一个新的bean,否则是没有被spring容器进行管理的
            BeanDefinitionBuilder monitorManageUtilBean = BeanDefinitionBuilder.rootBeanDefinition(MonitorManageUtil.class);
            monitorManageUtilBean.addPropertyValue("coreRestMaps", coreRestMaps);
            monitorManageUtilBean.addPropertyValue("authRestMaps", authRestMaps);
            monitorManageUtilBean.addPropertyValue("opRestMaps", opRestMaps);
            beanDefinitionRegistry.registerBeanDefinition("monitorManageUtilBean", MonitorManageUtilBean.getBeanDefinition());
        } catch (Exception e) {
            log.error("Init rest bean failed!", e);
            throw new BeanCreationException("初始化RestService失败!");
        }
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {


    }


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


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

2. 代码分析: 

首先通过环境变量获取配置内容,并与配置类进行绑定,从而后续方便获取配置项。

BindResult<MonitorRestBeanConfig> restServiceBindResult = Binder.get(environment).bind(CORE_URL_PREFIX, MonitorRestBeanConfig.class);

其次将获取到的配置内容注入到新注册的bean中,从而实现动态的注入自定义的bean。

3.其他配置及文件:

application.yaml

core:
  urls:
    pool-03:
      beanName: wuxi
      coreUrl: http://192.168.1.1:8080
      opUrl: http://192.168.1.2:8090
    pool-04:
      beanName: suzhou
      coreUrl: http://172.163.1.1:8080
      opUrl: http://172.163.1.2:8090

注意事项:

  • “CORE_URL_PREFIX”为配置文件中的前缀core
  • urls为参数名,需要与绑定的类的变量名一致才能进行参数的绑定
  • pool-03,pool-04为不同的资源池名称,不同的资源池有不同的地址,根据资源池名称进行查找使用不同的rest url

monitorManageUtilBean:

/**
* @author: xxx
* @date: 2020-09-11 15:50
* 该类作为存放所有的rest map的类,通过不同的需求获取不同的map,从而获取到对应pool的rest service
*/
@Data
public class MonitorManageUtil {

    private Map<String, CoreSponsorRestService> coreRestMaps = new ConcurrentHashMap<>();
    private Map<String, OpRestSponsorService> opRestMaps = new ConcurrentHashMap<>();
    private Map<String, ApiAuthRestService> authRestMaps = new ConcurrentHashMap<>();

}

 CoreSponsorRestService

/**
* @author: 
* @date: 2020-03-26 17:54
* 该类作为rest service 通过外部注入的不同的base url从而可以进行不同的rest url的访问
*/
@Slf4j
@Data
public class CoreSponsorRestService extends SponsorTemplateService {


    @Autowired
    @Qualifier("httpRestTemplate")
    private RestTemplate restTemplate;


    private String baseUrl = "";


    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public <T> T executeGet(String requestUrl, String token, String projectToken, String userId, Object auth, Class<T> responseType, Object... uriVariables) throws RestException {
        return requestRestApi(requestUrl, token, projectToken, userId, HttpMethod.GET, null, auth, responseType, uriVariables);
    }
}

三、使用方法示例:

@Autowired
private MonitorManageUtil monitorManageUtil;

JSONObject body = monitorManageUtil.getVasoCoreRestMaps().get(poolId).executeGet(
        "/api/web/xxxxxxx/{xxx-id}",
        token, "", userId, xxx, JSONObject.class, xxx-id);

  通过注入之前注册的自定义bean,使用poolId进行筛选出对应的rest bean 从而实现自动化配置。

四、总结

通过BeanDefinitionRegistryPostProcessor可以实现增加配置文件,从而自动增加注册bean。减去了通过代码手动添加配置,手动注入新增的配置。简化代码的同时,减少失误的概率,值得学习。