摘要:如何解决非集成情况下可能存在冲突的问题,有以下三种方案强制业务系统集成出现冲突时使用标明其自己已存在的冲突,以防止按注入出现的冲突异常。
我们开发内部用的二方库时往往需要定义一些bean,这些bean中有的可能已经被业务方系统配置使用了,在非SpringBoot方式集成中可能导致冲突。导致按type注入失败(因为存在两个已有的实现)。为什么要强调非SpringBoot呢,因为SpringBoot可以使用@ConditionOnMissingXxx条件注解。
如何解决非SpringBoot集成情况下可能存在冲突的问题,有以下三种方案:
1、强制业务系统集成出现冲突时使用@Qualifier标明其自己已存在的冲突bean,以防止按type注入出现的冲突异常。而我们自己的二方库也得加上@Qualifier,但是我们需要同时提供普通Spring集成和SpringBoot集成方式,那么加上@Qualifier又会成为我们自己给自己设置的麻烦,因为在存在@ConditionOnMissingBean的时候,我们极有可能沿用业务系统配置的bean,而此时我们是不知道其id的。从而导致@Qualifier失去了意义。
2、使用@Primary标注我们二方库里的bean为首选项,这样对于业务系统而言就会出现太过透明而无法知晓到底注入了它自己的还是二方库里的,而且如果业务系统存在特殊配置呢就被我们覆盖了。还有种方式是强制业务系统标注自己的bean为@Primary。
3、采用动态加载装配bean定义的方式。如果已存在则不装配,不存在才进行装配。效果类似于@ConditionMissingBean
由于第三种,对于集成方更为友好,所以采用第三种方案去实施。
主体思路:在Spring装配完所有的bean之后才实施我们的动态装配逻辑。
Spring提供了很多接口用于我们在bean初始化前后实现自定义逻辑,目前接触较多的有:BeanFactoryAware,ApplicationContextAware,ApplicationListener,InitializingBean,BeanPostProcessor。
针对这几个接口,我们梳理下bean初始化执行顺序:
bean本身的构造器初始化调用->BeanPostProcessor的前置处理调用postProcessBeforeInitialization->InitializingBean的afterPropertiesSet调用->BeanPostProcessor的后置处理调用postProcessAfterInitialization
要验证以上顺序也不难,可以写几个实现,打印一些日志,观察前后输出,去测试验证一下,简要截图如下:
注意:其中MyBean4是BeanPostProcessor的实现类。
这只讨论了单个bean的一个执行顺序,那么所有bean是哪个时候执行完的呢,或者说回调那个接口方法时,所有已经初始化完成并已完全就绪呢?我们来分析下这三个接口BeanFactoryAware,ApplicationContextAware,ApplicationListener
BeanFactoryAware,ApplicationContextAware这两个我们一般用来获取并保持容器上下文,刚开始我在想,是不是在setBeanFactory或者setApplicationContext的时候,所有bean已经就绪呢?
从截图来看,很遗憾,并没有如我们想象般那样。
接下来我们关注一下这个ApplicationListener。Spring为我们提供了以下几个事件以便于我们对容器生命周期的监听:
ContextRefreshedEvent:This event is published whenever the Spring Context is started or refreshed.
ContextStartedEvent:This event is published when the Spring Context is started.
ContextStoppedEvent:This event is published when the Spring Context is stopped. In practice you will not use this event very often. It can be handy for doing cleanup work, like closing connections.
ContextClosedEvent:This event is similar to the ContextStoppedEvent, but in this case the Context can not be re-started.
我们的关注点自然落在了ContextRefreshedEvent及ContextStartedEvent,但是按说服,似乎ContextRefrehedEvent更符合我们的需求,我们验证一下,是否该事件是在所有bean都就绪之后才会发出。
从日志中我们可以看到,在打印了onApplicationEvent日志之后,没有再出现beanPostProcessor的日志,说明确实是在所有bean都初始化完成之后才调用。
上述需求的动态装配实现代码如下:
public class SpringListener implements ApplicationListener{ private static final Logger log=LoggerFactory.getLogger(SpringListener.class); private static ApplicationContext applicationContext; @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { log.info("bizrule dynamic bean load start..."); applicationContext=contextRefreshedEvent.getApplicationContext(); XRuleEngine xRuleEngine=applicationContext.getBean(XRuleEngine.class); try { ConfigProps configProps=new ConfigProps(xRuleEngine); DynamicBeanLoader.dynamicLoadBizBean(applicationContext,EmployeeBasicService.class, configProps.getMasterDataVersion(),"",Integer.valueOf(configProps.getMasterDataTimeout()+"")); DynamicBeanLoader.dynamicLoadBizBean(applicationContext,EmployeeJobLevelService.class, configProps.getMasterDataVersion(),"",Integer.valueOf(configProps.getMasterDataTimeout()+"")); DynamicBeanLoader.dynamicLoadBizBean(applicationContext,EmployeeDeptService.class, configProps.getMasterDataVersion(),"",Integer.valueOf(configProps.getMasterDataTimeout()+"")); DynamicBeanLoader.dynamicLoadBean(applicationContext,MasterdataServiceImpl.class, Arrays.asList("secret::"+configProps.getMasterDataSecret(),"clientId::"+configProps.getMasterDataClientId()) .stream().map(v->v.split("::")).collect(Collectors.toMap(v->v[0],v->v[1]))); MasterdataService masterdataService=applicationContext.getBean(MasterdataService.class); DynamicBeanLoader.dynamicLoadBean(applicationContext,RcUserServiceImpl.class,new HashMap ()); RcUserService rcUserService=applicationContext.getBean(RcUserService.class); log.info("start autowired bizrule plugin beans"); BizAmountDecisionService bizAmountDecisionService= null; try { bizAmountDecisionService = applicationContext.getBean(BizAmountDecisionService.class); bizAmountDecisionService.setRcUserService(rcUserService); log.info("autowired beans for bizAmountDecisionService"); } catch (NoSuchBeanDefinitionException e) { log.info("didn"t find BizAmountDecisionService bean, ignore it"s RcUserService autowired. "); } UserReportLineService userReportLineService= null; try { userReportLineService = applicationContext.getBean(UserReportLineService.class); userReportLineService.setRcUserService(rcUserService); log.info("autowired beans for userReportLineService"); } catch (NoSuchBeanDefinitionException e) { log.info("didn"t find UserReportLineService bean, ignore it"s RcUserService autowired. "); } log.info("dynamic bizrule common bean loader over"); } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage(),e); } } public static ApplicationContext getApplicationContext() { return applicationContext; } public static void setApplicationContext(ApplicationContext applicationContext) { SpringListener.applicationContext = applicationContext; } }
public class DynamicBeanLoader { private static final Logger log=LoggerFactory.getLogger(DynamicBeanLoader.class); @Deprecated public static void dynamicLoadBean(ApplicationContext context,Resource... resources) { AutowireCapableBeanFactory factory = context.getAutowireCapableBeanFactory(); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) factory; XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader( registry); beanDefinitionReader.setResourceLoader(context); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(context)); try { for (int i = 0; i < resources.length; i++) { beanDefinitionReader.loadBeanDefinitions(resources[i]); } } catch (BeansException e) { e.printStackTrace(); log.error(e.getMessage(),e); } } public static void dynamicLoadBean(ApplicationContext context,Class clazz,MapSpringBoot Starter开发params){ if(isBeanLoaded(context,clazz)){ return; } AutowireCapableBeanFactory autowireCapableBeanFactory = context.getAutowireCapableBeanFactory(); DefaultListableBeanFactory beanFactory=(DefaultListableBeanFactory)autowireCapableBeanFactory; GenericBeanDefinition gbd = new GenericBeanDefinition(); gbd.setBeanClass(clazz); MutablePropertyValues mpv = new MutablePropertyValues(); params.entrySet().stream().forEach(e->{ mpv.add(e.getKey(), e.getValue()); }); gbd.setPropertyValues(mpv); beanFactory.registerBeanDefinition("xc"+clazz.getSimpleName(), gbd); } public static void dynamicLoadBizBean(ApplicationContext context,Class clazz,String version,String group,int timeout){ //略。。。。 } public static boolean isBeanLoaded(ApplicationContext context,Class clazz){ //之所以采用捕获异常而不是containsBean的形式,是因为containsBean仅支持按名称判断,在这种场景下有局限性。 try { context.getBean(clazz); log.info("find {} in system, ignore load",clazz.getName()); return true; } catch (NoSuchBeanDefinitionException e) { log.info("not find {} in system, will being load",clazz.getName()); return false; } } }
我们同时提供SpringBoot starter集成。
先贴上官方文档:https://docs.spring.io/spring...
官方文档写得很简单很抽象。不过提供了思路,SpringBoot启动的时候会去寻找META-INF/spring.factories文件,去加载其中的配置进行相应的初始化。默认配置由SpringBoot Autoconfigure模块提供,注意到其中的片段:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,
这里定义了自动配置的类,我们可找到对应的类看看实现。
然后我们可以随便挑几个starter看看,总结下思路步骤:
1、定义AutoConfiguration类
2、如果有需要在application.properties加些配置,可以定义XxxProperties类
3、定义META-INF/spring.factories文件
具体照着代码说明吧:
pom依赖添加,这里版本就不列出来了。
org.springframework.boot spring-boot org.springframework.boot spring-boot-autoconfigure
XxxProperties类定义:
@ConfigurationProperties(prefix = "xrulecenter.biz") public class AmountDesionProperties { }
这样我们就可以在application.properties中使用 xrulecenter.biz.属性=value 来配置
AutoConfiguration类定义
@Configuration @EnableConfigurationProperties(value = {GetAuditorProperties.class}) @ConditionalOnClass(BizAuditorServiceImpl.class) @ConditionalOnProperty(prefix = "xrulecenter.bizrule.enabled",name={"getauditor"},havingValue = "true",matchIfMissing = true) public class GetAuditorAutoConfiguration { @Autowired private GetAuditorProperties getAuditorProperties; @Bean @ConditionalOnMissingBean public SpringListener xcBizRuleSpringListener(){ return new SpringListener(); } @Bean @ConditionalOnMissingBean public BizAuditorService bizAuditorService(){ return new BizAuditorServiceImpl(); } @Bean @ConditionalOnMissingBean public UserReportLineService xcUserReportLineService(){ return new UserReportLineServiceImpl(); } }
这里有几个注解需要注意一下:
@EnableConfigurationProperties使我们前面定义的properties能生效
@ConditionalOnXxx 条件注入生效注解,其中@ConditionalOnProperty需要关注一下:通过它我们可以配置自己starter模块的开关配置,而不是引入依赖即开启,让用户有选择地打开和关闭。
havingValue = "true",matchIfMissing = true这两个属性同时添加的意思是:如果用户在application.properties配置了xrulecenter.bizrule.enabled=true或者没有此配置都将使该starter生效。
定义spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.auditor.GetAuditorAutoConfiguration
这样我们的starter就完工了。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69598.html
摘要:说明这篇文章是我第一次认真阅读阿里巴巴开发手册终极版的笔记。说明本手册明确防止是调用者的责任。一年半载后,那么单元测试几乎处于废弃状态。好的单元测试能够最大限度地规避线上故障。 说明 这篇文章是我第一次(认真)阅读《阿里巴巴 Java 开发手册(终极版)》的笔记。手册本身对规范的讲解已经非常详细了,如果你已经有一定的开发经验并且有良好的编码习惯和意识,会发现大部分规范是符合常识的。所以...
摘要:的报告进一步证实了与成功项目最密切的因素是良好的需求管理,也就是项目的范围管理,特别是管理好项目的变更。需求管理的第一步就是要梳理不同来源的需求,主要包括从产品定位出发外部用户反馈竞争对手情况市场变化以及内部运营人员客服人员开发人员的反馈。 showImg(https://upload-images.jianshu.io/upload_images/2509688-ac38883baf...
摘要:微服务项目的依赖关系在微服务化架构中软件项目被拆分成多个自治的服务服务之间通过网络协议进行调用通常使用透明的远程调用在领域每个服务上线后对外输出的接口为一个包在微服务领域包被分为一方库二方库三方库一方库本服务在进程内依赖的包二方库在服务外通 微服务项目的依赖关系 在微服务化架构中, 软件项目被拆分成多个自治的服务, 服务之间通过网络协议进行调用, 通常使用透明的 RPC 远程调用. 在...
阅读 1373·2021-11-22 09:34
阅读 2583·2021-11-12 10:36
阅读 1113·2021-11-11 16:55
阅读 2326·2020-06-22 14:43
阅读 1460·2019-08-30 15:55
阅读 1978·2019-08-30 15:53
阅读 1766·2019-08-30 10:50
阅读 1220·2019-08-29 12:15