spring NamedContextFactory實(shí)現(xiàn)服務(wù)隔離的示例詳解
前言
假設(shè)我們有個(gè)場景,我們需要實(shí)現(xiàn)服務(wù)之間的數(shù)據(jù)隔離、配置隔離、依賴的spring bean之間隔離。大家會(huì)有什么實(shí)現(xiàn)思路?今天給大家介紹spring-cloud-context里面有個(gè)NamedContextFactory可以達(dá)到上面的效果
NamedContextFactory簡介
NamedContextFactory可以實(shí)現(xiàn)子容器,通過它創(chuàng)建子容器,然后通過NamedContextFactory.Specification可以定制子容器會(huì)用到的bean。
所以為什么通過NamedContextFactory可以達(dá)到數(shù)據(jù)隔離、配置隔離、依賴的spring bean之間隔離,本質(zhì)就是利用NamedContextFactory為不同的服務(wù),創(chuàng)建出不同的子容器,子容器之間彼此不共享,從而達(dá)到隔離的效果
下面通過一個(gè)示例來講解
示例
注: 示例就模擬一個(gè)用戶注冊成功后發(fā)送華為云短信,下單成功后發(fā)送阿里云短信為例子
1、模擬定義短信接口
public interface SmsService { void send(String phone, String content); }
2、模擬定義相應(yīng)短信實(shí)現(xiàn)類
public class DefaultSmsService implements SmsService { @Override public void send(String phone, String content) { System.out.printf("send to %s content %s used default sms%n", phone, content); } }
public class AliyunSmsService implements SmsService { @Override public void send(String phone, String content) { System.out.printf("send to %s content %s used aliyun sms%n", phone, content); } }
public class HuaWeiSmsService implements SmsService { @Override public void send(String phone, String content) { System.out.printf("send to %s content %s used huawei sms%n", phone, content); } }
3、自定義短信默認(rèn)配置類
@Configuration public class DefaultSmsClientConfiguration { @Bean @ConditionalOnMissingBean public SmsService smsService(){ return new DefaultSmsService(); } }
4、定制短信需要的子容器NamedContextFactory.Specification
public class SmsClientSpecification implements NamedContextFactory.Specification{ private String name; private Class<?>[] configuration; public SmsClientSpecification() { } public SmsClientSpecification(String name, Class<?>[] configuration) { this.name = name; this.configuration = configuration; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Class<?>[] getConfiguration() { return configuration; } public void setConfiguration(Class<?>[] configuration) { this.configuration = configuration; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SmsClientSpecification that = (SmsClientSpecification) o; return Arrays.equals(configuration, that.configuration) && Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(configuration, name); } @Override public String toString() { return new StringBuilder("SmsSpecification{").append("name='") .append(name).append("', ").append("configuration=") .append(Arrays.toString(configuration)).append("}").toString(); } }
屬性講解
name: 子容器的名稱(示例中我們會(huì)把用戶服務(wù)名和訂單服務(wù)名當(dāng)成子容器名稱)
configuration: name子容器需要的configuration
NamedContextFactory.Specification的作用是當(dāng)創(chuàng)建子容器時(shí),如果容器的name匹配了Specification的name,則會(huì)加載 Specification對應(yīng)Configuration類,并將Configuration類里面標(biāo)注@Bean的返回值注入到子容器中
5、為不同的服務(wù)創(chuàng)建不同的SmsClientSpecification并注入到spring容器中
@Configuration @Import(SmsClientConfigurationRegistrar.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SmsClient { /** * Synonym for name (the name of the client). * * @see #name() * @return name of the Sms client */ String value() default ""; /** * The name of the sms client, uniquely identifying a set of client resources, * @return name of the Sms client */ String name() default ""; /** * A custom <code>@Configuration</code> for the sms client. Can contain override * <code>@Bean</code> definition for the pieces that make up the client */ Class<?>[] configuration() default {}; }
@Configuration @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @Documented @Import(SmsClientConfigurationRegistrar.class) public @interface SmsClients { SmsClient[] value() default {}; Class<?>[] defaultConfiguration() default {}; }
注: 利用import機(jī)制,將SmsClientSpecification注入到spring容器
public class SmsClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map<String, Object> attrs = metadata .getAnnotationAttributes(SmsClients.class.getName(), true); if (attrs != null && attrs.containsKey("value")) { AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value"); for (AnnotationAttributes client : clients) { registerClientConfiguration(registry, getClientName(client), client.get("configuration")); } } if (attrs != null && attrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } registerClientConfiguration(registry, name, attrs.get("defaultConfiguration")); } Map<String, Object> client = metadata .getAnnotationAttributes(SmsClient.class.getName(), true); String name = getClientName(client); if (name != null) { registerClientConfiguration(registry, name, client.get("configuration")); } } private String getClientName(Map<String, Object> client) { if (client == null) { return null; } String value = (String) client.get("value"); if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException( "Either 'name' or 'value' must be provided in @SmsClient"); } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(SmsClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + ".SmsClientSpecification", builder.getBeanDefinition()); } }
6、創(chuàng)建短信NameContextFactory
public class SmsClientNameContextFactory extends NamedContextFactory<SmsClientSpecification> { public SmsClientNameContextFactory() { super(DefaultSmsClientConfiguration.class, "sms", "sms.client.name"); } public SmsService getSmsService(String serviceName) { return getInstance(serviceName, SmsService.class); } }
注: super三個(gè)參數(shù)講解
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) { this.defaultConfigType = defaultConfigType; this.propertySourceName = propertySourceName; this.propertyName = propertyName; }
defaultConfigType: 默認(rèn)配置類,NamedContextFactory創(chuàng)建子容器時(shí),默認(rèn)就會(huì)加載該配置類,該配置類主要用來做兜底,當(dāng)找不到容器為name的configuration,則會(huì)使用該配置類
propertySourceName: 給propertySource取個(gè)名稱
propertyName: 子容器可以通過讀取配置propertyName來獲取容器名。當(dāng)創(chuàng)建子容器時(shí)通常會(huì)提供子容器的容器name。子容器中的Environment會(huì)被寫入一條配置,sms.client.name=容器name
7、將SmsClientNameContextFactory注入到spring容器
@Bean @ConditionalOnMissingBean public SmsClientNameContextFactory smsClientNameContextFactory(@Autowired(required = false) List<SmsClientSpecification> smsSpecifications){ SmsClientNameContextFactory smsClientNameContextFactory = new SmsClientNameContextFactory(); smsClientNameContextFactory.setConfigurations(smsSpecifications); return smsClientNameContextFactory; }
8、創(chuàng)建不同的短信配置類
public class AliyunSmsClientConfiguration { @ConditionalOnMissingBean @Bean public SmsService smsService() { return new AliyunSmsService(); } }
public class HuaWeiSmsClientConfiguration { @ConditionalOnMissingBean @Bean public SmsService smsService() { return new HuaWeiSmsService(); } }
注: 因?yàn)樯鲜雠渲弥恍璞蛔尤萜骷虞d,因此不需要加 @Configuration
9、為用戶服務(wù)和訂單服務(wù)指定NamedContextFactory.Specification
@Configuration @SmsClients(value = {@SmsClient(name = OrderService.SERVICE_NAME, configuration = AliyunSmsClientConfiguration.class), @SmsClient(name = UserService.SERVICE_NAME, configuration = HuaWeiSmsClientConfiguration.class)}) public class SmsClientAutoConfiguration { }
10、測試
模擬用戶注冊
@Service @RequiredArgsConstructor public class UserService { private final ApplicationContext applicationContext; public static final String SERVICE_NAME = "userService"; public void registerUser(String userName, String password,String mobile){ System.out.println("注冊用戶"+userName+"成功"); UserRegisterEvent event = new UserRegisterEvent(userName,password,mobile); applicationContext.publishEvent(event); } }
@Component @RequiredArgsConstructor public class UserRegisterListener { private final SmsClientNameContextFactory smsClientNameContextFactory; @EventListener @Async public void listener(UserRegisterEvent event) { SmsService smsService = smsClientNameContextFactory.getSmsService(UserService.SERVICE_NAME); smsService.send(event.getMobile(), "恭喜您注冊成功!初始密碼為:"+event.getPassword()+",請盡快修改密碼!"); } }
核心:
SmsService smsService = smsClientNameContextFactory.getSmsService(UserService.SERVICE_NAME);
和 @SmsClient(name = UserService.SERVICE_NAME)對應(yīng)起來
運(yùn)行查看控制臺(tái)
當(dāng)服務(wù)名不匹配時(shí),再觀察控制臺(tái)
發(fā)現(xiàn)此時(shí)是走默認(rèn)配置
總結(jié)
本文主要是聊下通過NamedContextFactory來實(shí)現(xiàn)服務(wù)隔離,核心點(diǎn)就是通過創(chuàng)建不同子容器進(jìn)行隔離。這種方式在ribbon、openfeign、以及l(fā)oadbalancer都有類似的實(shí)現(xiàn),感興趣朋友可以查閱其源碼。不過這邊有細(xì)節(jié)點(diǎn)需要注意,因?yàn)镹amedContextFactory默認(rèn)是懶加載創(chuàng)建子容器,所以可能第一次調(diào)用會(huì)比較慢。這也是ribbon第一次調(diào)用慢的原因
以上就是spring NamedContextFactory實(shí)現(xiàn)服務(wù)隔離的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于spring NamedContextFactory服務(wù)隔離的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java如何將int數(shù)組轉(zhuǎn)化為Integer數(shù)組
這篇文章主要介紹了java如何將int數(shù)組轉(zhuǎn)化為Integer數(shù)組,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Spring內(nèi)置任務(wù)調(diào)度如何實(shí)現(xiàn)添加、取消與重置詳解
任務(wù)調(diào)度是我們?nèi)粘i_發(fā)中經(jīng)常會(huì)碰到的,下面這篇文章主要給大家介紹了關(guān)于Spring內(nèi)置任務(wù)調(diào)度如何實(shí)現(xiàn)添加、取消與重置的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10SpringBoot整合Liquibase實(shí)現(xiàn)對數(shù)據(jù)庫管理和遷移
Liquibase是一個(gè)用于用于跟蹤、管理和應(yīng)用數(shù)據(jù)庫變化的開源工具,通過日志文件(changelog)的形式記錄數(shù)據(jù)庫的變更(changeset),然后執(zhí)行日志文件中的修改,將數(shù)據(jù)庫更新或回滾(rollback)到一致的狀態(tài),本文主要介紹SpringBoot與Liquibase的集成,需要的朋友可以參考下2024-11-11