之前有写过一篇文章【设计模式】基于SpringBoot实现的策略模式 ,通过策略模式可以实现对于不同的参数,调用不同的bean,实现对于代码的最少侵入。基于这种方式,可以通过读取配置文件,注入到bean中,实现多个bean的配置方法。但是这种方式存在的问题就是:
- 配置文件每增加一个配置,需要增加一个bean配置类。
- 多个配置增加的时候容易遗漏,容易出错。
因此,本文将针对这种情况,基于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。减去了通过代码手动添加配置,手动注入新增的配置。简化代码的同时,减少失误的概率,值得学习。