聊聊如何利用spring实现服务隔离

news/2024/11/14 13:13:08/文章来源:https://www.cnblogs.com/linyb-geek/p/18014415

前言

假设我们有个场景,我们需要实现服务之间的数据隔离、配置隔离、依赖的spring bean之间隔离。大家会有什么实现思路?今天给大家介绍spring-cloud-context里面有个NamedContextFactory可以达到上面的效果

NamedContextFactory简介

NamedContextFactory可以实现子容器,通过它创建子容器,然后通过NamedContextFactory.Specification可以定制子容器会用到的bean。

所以为什么通过NamedContextFactory可以达到数据隔离、配置隔离、依赖的spring bean之间隔离,本质就是利用NamedContextFactory为不同的服务,创建出不同的子容器,子容器之间彼此不共享,从而达到隔离的效果

下面通过一个示例来讲解

示例

注: 示例就模拟一个用户注册成功后发送华为云短信,下单成功后发送阿里云短信为例子

1、模拟定义短信接口

public interface SmsService {void send(String phone, String content);
}

2、模拟定义相应短信实现类

public class DefaultSmsService implements SmsService {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic void send(String phone, String content) {System.out.printf("send to %s content %s used huawei sms%n", phone, content);}
}

3、自定义短信默认配置类

@Configuration
public class DefaultSmsClientConfiguration {@Bean@ConditionalOnMissingBeanpublic 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;}@Overridepublic String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic Class<?>[] getConfiguration() {return configuration;}public void setConfiguration(Class<?>[] configuration) {this.configuration = configuration;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(configuration, name);}@Overridepublic String toString() {return new StringBuilder("SmsSpecification{").append("name='").append(name).append("', ").append("configuration=").append(Arrays.toString(configuration)).append("}").toString();}
}

属性讲解

name: 子容器的名称(示例中我们会把用户服务名和订单服务名当成子容器名称)

configuration: name子容器需要的configuration

NamedContextFactory.Specification的作用是当创建子容器时,如果容器的name匹配了Specification的name,则会加载 Specification对应Configuration类,并将Configuration类里面标注@Bean的返回值注入到子容器中

5、为不同的服务创建不同的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机制,将SmsClientSpecification注入到spring容器

public class SmsClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic 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、创建短信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三个参数讲解

public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,String propertyName) {this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;}

defaultConfigType: 默认配置类,NamedContextFactory创建子容器时,默认就会加载该配置类,该配置类主要用来做兜底,当找不到容器为name的configuration,则会使用该配置类
propertySourceName: 给propertySource取个名称
propertyName: 子容器可以通过读取配置propertyName来获取容器名。当创建子容器时通常会提供子容器的容器name。子容器中的Environment会被写入一条配置,sms.client.name=容器name

7、将SmsClientNameContextFactory注入到spring容器

   @Bean@ConditionalOnMissingBeanpublic SmsClientNameContextFactory smsClientNameContextFactory(@Autowired(required = false) List<SmsClientSpecification> smsSpecifications){SmsClientNameContextFactory smsClientNameContextFactory = new SmsClientNameContextFactory();smsClientNameContextFactory.setConfigurations(smsSpecifications);return smsClientNameContextFactory;}

8、创建不同的短信配置类

public class AliyunSmsClientConfiguration {@ConditionalOnMissingBean@Beanpublic SmsService smsService() {return new AliyunSmsService();}
}
public class HuaWeiSmsClientConfiguration {@ConditionalOnMissingBean@Beanpublic SmsService smsService() {return new HuaWeiSmsService();}
}

注: 因为上述配置只需被子容器加载,因此不需要加 @Configuration

9、为用户服务和订单服务指定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@Asyncpublic 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)对应起来

运行查看控制台

在这里插入图片描述
当服务名不匹配时,再观察控制台

发现此时是走默认配置

总结

本文主要是聊下通过NamedContextFactory来实现服务隔离,核心点就是通过创建不同子容器进行隔离。这种方式在ribbon、openfeign、以及loadbalancer都有类似的实现,感兴趣朋友可以查阅其源码。不过这边有细节点需要注意,因为NamedContextFactory默认是懒加载创建子容器,所以可能第一次调用会比较慢。这也是ribbon第一次调用慢的原因

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-named-context-factory

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/709883.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

让机台数据传输更高效可靠,一文了解!

在汽车制造业中,机台数据传输和管理是一个关键环节,它涉及到生产效率、产品质量和企业运营的多个方面。以下是一些机台数据传输和管理的关键点:车载通信技术:随着汽车智能化的提升,车载通信技术变得尤为重要。车内总线通信与车载无线通信技术的提升,使得智能电动汽车成为…

openpyxl styles 模块

styles 模块说明 styles 模块提供了许多用于设置和控制单元格样式的类和方法。这些类和方法可以帮助你定制 Excel 工作表中单元格的外观,包括字体样式、边框、填充颜色等。 styles 模块主要功能 Font(字体):Font 类用于定义和修改单元格中文本的字体样式,如字体名称、大小…

dotnet 命令行工具解决方案 PomeloCli

目录PomeloCli 是什么为什么实现太多的工具太少的规范基于二进制拷贝分发难以为继快速开始1. 引用 PomeloCli 开发命令行应用2. 引用 PomeloCli 开发命令行插件开发命令行插件搭建私有 nuget 服务发布命令行插件3. 使用 PomeloCli 集成已发布插件安装命令行宿主集成命令行插件…

华为云CodeArts 12大安全防护机制,端到端全面保障软件供应链安全!

华为云CodeArts推出软件供应链安全解决方案,对软件作业流12个安全威胁点加对应防护机制。全球网络安全事件频发不断,企业纷纷损失惨重。2021年11月,知名logo4j漏洞波及全球多达6万款开源软件,70%以上企业受影响。2022年3月,大型加油站服务商遭到勒索软件攻击,要求其支付2…

MLOps 学习之旅「GitHub 热点速览」

又是 AI 神仙打架的一周,上周 OpenAI 发布了最新的 GPT-4o 模型,而谷歌也紧跟着开源了 Gemma 2 模型。随着 AI 大模型不断地变强,各大科技巨头正利用它们重塑自家的产品,这也让大模型算法工程师变得炙手可热,相关岗位需求正旺。 对于普通程序员来说,想要转型成为大模型算…

【博客园发文技巧】不离开编辑页面,批量添加图片链接和设置图片大小

参考文档:https://www.cnblogs.com/sanshi/p/3794796.html 起因 在博客园写文章,有时需要上传好多大图片,如果这些图片过大,则会导致页面变形。 因此有一个实际的需求,能够在博客园的编辑页面,直接批量修改所有图片的大小,然后给这些图片添加链接,以便点击时转到大图。…

video2blog 视频转图文AI小工具正式开源啦

前言 最近对一些小细节做了很多处理,但是其实还是有非常多的问题,没办法时间毕竟时间有限。为什么在这个时候开源,因为主要功能可以全部跑通了,分支暂时没开发的功能也可以通过其他的工具来替代。 这个工具开发初衷(想法来源),我之前有一篇文章有详细的说明,有兴趣的可…

组策略和bginfo

简介 很多的IT管理员都希望终端操作用户达到一个计算机脱盲的水平,但是理想很丰满,现实很骨感。人生不如事十之八九。 终端用户真的一言难尽。 简单的帮我们看一下CPU,内存,IP地址,这些基础信息,他们做不到。 好在微软发布了bginfo这个软件,BgInfo - Sysinternals | Mic…

css小三角文字平移加旋转

<view class="sanjiao"><view class="slanted-text">饿了么</view></view> /* 三角 */ .sanjiao {width: 0;height: 0;border-left: 40px solid transparent;border-right: 40px solid red;border-bottom: 40px solid transparent…

20240520刷题总结

T1(状态置换,搜索与dp, dp存值结构体) T376。 还是从搜索角度去考虑:时间,前i物品,最多拿多少。 这样我们去设计状态,我一开始设置:时间,前i,值是拿多少。会发现这样会爆。 其实换一下,优化效果更好。前i物品,最多拿j,用的最少时间。 实际转移就是背包。存值就存结构…

SAP:CX_SY_READ_SRC_LINE_TOO_LONG解决

在ABAP程序编辑器中,确保每行的字符数小于72个,将光标放到行结尾,就能在右下角能看到字符总数。 只要行字符都小于72个,dump就不会再出现了。。。。 还有一种方法就是调整一下abap editor的配置,勾上Downwards-Compatible Line Length(72)。在ABAP编辑器菜单点击“实用程序…