spring NamedContextFactory實現(xiàn)服務(wù)隔離的示例詳解
前言
假設(shè)我們有個場景,我們需要實現(xiàn)服務(wù)之間的數(shù)據(jù)隔離、配置隔離、依賴的spring bean之間隔離。大家會有什么實現(xiàn)思路?今天給大家介紹spring-cloud-context里面有個NamedContextFactory可以達(dá)到上面的效果
NamedContextFactory簡介
NamedContextFactory可以實現(xiàn)子容器,通過它創(chuàng)建子容器,然后通過NamedContextFactory.Specification可以定制子容器會用到的bean。
所以為什么通過NamedContextFactory可以達(dá)到數(shù)據(jù)隔離、配置隔離、依賴的spring bean之間隔離,本質(zhì)就是利用NamedContextFactory為不同的服務(wù),創(chuàng)建出不同的子容器,子容器之間彼此不共享,從而達(dá)到隔離的效果
下面通過一個示例來講解
示例
注: 示例就模擬一個用戶注冊成功后發(fā)送華為云短信,下單成功后發(fā)送阿里云短信為例子
1、模擬定義短信接口
public interface SmsService {
void send(String phone, String content);
}
2、模擬定義相應(yīng)短信實現(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: 子容器的名稱(示例中我們會把用戶服務(wù)名和訂單服務(wù)名當(dāng)成子容器名稱)
configuration: name子容器需要的configuration
NamedContextFactory.Specification的作用是當(dāng)創(chuàng)建子容器時,如果容器的name匹配了Specification的name,則會加載 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三個參數(shù)講解
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
defaultConfigType: 默認(rèn)配置類,NamedContextFactory創(chuàng)建子容器時,默認(rèn)就會加載該配置類,該配置類主要用來做兜底,當(dāng)找不到容器為name的configuration,則會使用該配置類
propertySourceName: 給propertySource取個名稱
propertyName: 子容器可以通過讀取配置propertyName來獲取容器名。當(dāng)創(chuàng)建子容器時通常會提供子容器的容器name。子容器中的Environment會被寫入一條配置,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();
}
}
注: 因為上述配置只需被子容器加載,因此不需要加 @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)行查看控制臺

當(dāng)服務(wù)名不匹配時,再觀察控制臺

發(fā)現(xiàn)此時是走默認(rèn)配置
總結(jié)
本文主要是聊下通過NamedContextFactory來實現(xiàn)服務(wù)隔離,核心點就是通過創(chuàng)建不同子容器進(jìn)行隔離。這種方式在ribbon、openfeign、以及l(fā)oadbalancer都有類似的實現(xiàn),感興趣朋友可以查閱其源碼。不過這邊有細(xì)節(jié)點需要注意,因為NamedContextFactory默認(rèn)是懶加載創(chuàng)建子容器,所以可能第一次調(diào)用會比較慢。這也是ribbon第一次調(diào)用慢的原因
以上就是spring NamedContextFactory實現(xiàn)服務(wù)隔離的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于spring NamedContextFactory服務(wù)隔離的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot集成JWT實現(xiàn)登錄注冊的示例代碼
本文主要介紹了Springboot集成JWT實現(xiàn)登錄注冊的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
Spring MVC之WebApplicationContext_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了Spring MVC之WebApplicationContext的相關(guān)資料,需要的朋友可以參考下2017-08-08
Springboot使用異步方法優(yōu)化Service邏輯,提高接口響應(yīng)速度方式
這篇文章主要介紹了Springboot使用異步方法優(yōu)化Service邏輯,提高接口響應(yīng)速度方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-06-06
解決報錯:java.lang.IllegalStateException: Failed to&nb
在項目開發(fā)中,可能會遇到Elasticsearch啟動報錯的問題,原因可能包括版本不一致、端口配置錯誤、配置文件不匹配及服務(wù)未啟動等,解決方法包括檢查進(jìn)程、重啟服務(wù)等,這些經(jīng)驗可以幫助開發(fā)者快速定位問題并解決,保證項目順利運(yùn)行2024-10-10
Java中的PrintWriter 介紹_動力節(jié)點Java學(xué)院整理
PrintWriter 是字符類型的打印輸出流,它繼承于Writer。接下來通過本文給大家介紹java中的 PrintWriter 相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧2017-05-05
SpringBoot實現(xiàn)登錄攔截器超詳細(xì)教程分享
對于管理系統(tǒng)或其他需要用戶登錄的系統(tǒng),登錄驗證都是必不可少的環(huán)節(jié),尤其在?SpringBoot?開發(fā)的項目中。本文為大家準(zhǔn)備了超詳細(xì)的SpringBoot實現(xiàn)登錄攔截器方法,快收藏一波吧2023-02-02

