外掛整合框架——多專案分散式模式下的框架設計
1、場景
作為一個網賺類小遊戲開發公司,會快速的開發不同的遊戲產品,整體架構採用微服務模式,中臺模組包括三個功能模組(賬戶、使用者、訂單),所有遊戲業務專案都與中臺互動,從而只關注遊戲業務端的功能開發即可。這是根據業務拆分的微服務模組。既然使用了微服務,就涉及到不同服務間的通訊,這必然會使用到各種中介軟體(比如rpc-dubbo/motan,訊息中介軟體-rocketMQ/kafka,Redis等)。
試想幾天就要開發一個新產品,如果沒有一個專門的中介軟體整合框架,這些重複的中介軟體配置管理起來既混亂,又浪費開發人員時間。
2、整合框架pepper-boot
整合框架最初命名caf,其中集成了apollo,motan,redis,rocketMQ,kafka,mybatis,prometheus(業務效能監控),後面將外掛的整合抽出為pepper-boot,監控抽出為pepper-metrics,這裡我們只介紹pepper-boot,pepper-metrics可閱讀metrics模組。
所有產品要做到獨立隔離,自然就會想到namespace的概念,因此專案的配置、包括例項名都是通過namespace進行隔離的,每個專案擁有唯一的namespqce。理解了這一點對於下面涉及到的引數解析和例項名稱的設定就清楚了。
motan整合
建立了一個啟用註解@EnableMotan
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(EnableMotans.class) @Import(MotanRegistrar.class) public @interface EnableMotan { String namespace()default "default"; }
其中通過@Import注入了MotanRegistrar類,這個類就是在註冊類元資訊的時候註冊響應的配置處理類
public class MotanRegistrar extends AbstractRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {final String annotationName = EnableMotan.class.getName(); final String annotationsName = EnableMotans.class.getName(); registerBeanDefinitions(importingClassMetadata, registry, annotationName, annotationsName); } protected void dealOne(BeanDefinitionRegistry registry, AnnotationAttributes oneAttributes) { String namespace = oneAttributes.getString("namespace"); Assert.isTrue(StringUtils.isNotEmpty(namespace), "namespace must be specified!");
//可以看到這裡註冊了一個後置處理器,作用就是將apollo中配置的引數設定到各個config中 BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + MotanBeanPostProcessor.class.getSimpleName(), MotanBeanPostProcessor.class ); //註冊中心配置Bean,例項名加字首namespace BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + RegistryConfigBean.class.getSimpleName(), RegistryConfigBean.class );
//協議配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + ProtocolConfigBean.class.getSimpleName(), ProtocolConfigBean.class );
//服務端配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + BasicServiceConfigBean.class.getSimpleName(), BasicServiceConfigBean.class );
//客戶端配置Bean BeanRegistrationUtil.registerBeanDefinitionIfBeanNameNotExists( registry, namespace + BasicRefererConfigBean.class.getSimpleName(), BasicRefererConfigBean.class ); } }
這裡說下ImportBeanDefinitionRegistrar,spring提供這個類主要是用來動態註冊bean的。
所有實現了該介面的類的都會被ConfigurationClassPostProcessor處理,ConfigurationClassPostProcessor實現了BeanFactoryPostProcessor介面,所以ImportBeanDefinitionRegistrar中動態註冊的bean是優先與依賴其的bean初始化的,也能被aop、validator等機制處理。
另外可以看到在裡面註冊了一個後置處理器
public class MotanBeanPostProcessor extends BaseMotanConfiguration implements BeanPostProcessor, Ordered, EnvironmentAware, BeanFactoryAware { @Autowired protected CustomizedPropertiesBinder binder; private Environment environment; private BeanFactory beanFactory; public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RegistryConfigBean) { String sourceName = StringUtils.substringBefore(beanName, RegistryConfigBean.class.getSimpleName()); RegistryConfigBean registryConfigBean = (RegistryConfigBean) bean; initRegistryConfig(registryConfigBean); String globalPrefix = PREFIX_APP_MOTAN + ".registry"; String nsPrefix = PREFIX_APP_MOTAN + "." + sourceName + ".registry"; if (StringUtils.isAllEmpty(environment.getProperty(globalPrefix + ".address"), environment.getProperty(nsPrefix + ".address"))) { throw new IllegalArgumentException(String.format("%s or %s can not be null!!", globalPrefix, nsPrefix)); } if (StringUtils.isNotEmpty(environment.getProperty(globalPrefix + ".address"))){ Bindable<?> target = Bindable.of(RegistryConfigBean.class).withExistingValue(registryConfigBean); binder.bind(globalPrefix, target); } Bindable<?> target = Bindable.of(RegistryConfigBean.class).withExistingValue(registryConfigBean); binder.bind(nsPrefix, target); } else if (bean instanceof ProtocolConfigBean) { String sourceName = StringUtils.substringBefore(beanName, ProtocolConfigBean.class.getSimpleName()); ProtocolConfigBean protocolConfigBean = (ProtocolConfigBean) bean; initProtocolConfig(protocolConfigBean); Bindable<?> target = Bindable.of(ProtocolConfigBean.class).withExistingValue(protocolConfigBean); binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".protocol", target); protocolConfigBean.setBeanName(beanName); } else if (bean instanceof BasicServiceConfigBean) { String sourceName = StringUtils.substringBefore(beanName, BasicServiceConfigBean.class.getSimpleName()); String registryBeanName = sourceName + RegistryConfigBean.class.getSimpleName(); RegistryConfigBean registryConfigBean = beanFactory.getBean(registryBeanName, RegistryConfigBean.class); Assert.notNull(registryConfigBean, String.format("%s does not existed in spring context, pls check!", registryBeanName)); String protocolBeanName = sourceName + ProtocolConfigBean.class.getSimpleName(); ProtocolConfigBean protocolConfigBean = beanFactory.getBean(protocolBeanName, ProtocolConfigBean.class); Assert.notNull(protocolConfigBean, String.format("%s does not existed in spring context, pls check!", protocolBeanName)); String portKey = PREFIX_APP_MOTAN + "." + sourceName + ".port"; String port = environment.getProperty(portKey); if (StringUtils.isEmpty(port)) { port = "10010"; } Assert.isTrue(StringUtils.isNotEmpty(port) && NumberUtils.isCreatable(port), String.format("%s=%s must be not null! and must be a number!", portKey, port)); BasicServiceConfigBean basicServiceConfigBean = (BasicServiceConfigBean) bean; initBasicServiceConfig(registryConfigBean, protocolConfigBean, Integer.parseInt(port), basicServiceConfigBean); Bindable<?> target = Bindable.of(BasicServiceConfigBean.class).withExistingValue(basicServiceConfigBean); binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".basic-service", target); } else if (bean instanceof BasicRefererConfigBean) { String sourceName = StringUtils.substringBefore(beanName, BasicRefererConfigBean.class.getSimpleName()); String registryBeanName = sourceName + RegistryConfigBean.class.getSimpleName(); RegistryConfigBean registryConfigBean = beanFactory.getBean(registryBeanName, RegistryConfigBean.class); Assert.notNull(registryConfigBean, String.format("%s does not existed in spring context, pls check!", registryBeanName)); String protocolBeanName = sourceName + ProtocolConfigBean.class.getSimpleName(); ProtocolConfigBean protocolConfigBean = beanFactory.getBean(protocolBeanName, ProtocolConfigBean.class); Assert.notNull(protocolConfigBean, String.format("%s does not existed in spring context, pls check!", protocolBeanName)); BasicRefererConfigBean basicRefererConfigBean = (BasicRefererConfigBean) bean; initBasicRefererConfig(registryConfigBean, protocolConfigBean, basicRefererConfigBean); Bindable<?> target = Bindable.of(BasicRefererConfigBean.class).withExistingValue(basicRefererConfigBean); binder.bind(PREFIX_APP_MOTAN + "." + sourceName + ".basic-referer", target); } return bean; } @Override public int getOrder() { return 0; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }