spring NamedContextFactory实现服务隔离的示例详解

 更新时间:2024年05月22日 10:24:09   作者:linyb极客之路  
假设我们有个场景,我们需要实现服务之间的数据隔离、配置隔离、依赖的spring bean之间隔离,大家会有什么实现思路?今天给大家介绍spring-cloud-context里面有个NamedContextFactory可以达到上面的效果,需要的朋友可以参考下

前言

假设我们有个场景,我们需要实现服务之间的数据隔离、配置隔离、依赖的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 {
    @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、自定义短信默认配置类

@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: 子容器的名称(示例中我们会把用户服务名和订单服务名当成子容器名称)

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 {

	@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、创建短信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
    @ConditionalOnMissingBean
    public SmsClientNameContextFactory smsClientNameContextFactory(@Autowired(required = false) List<SmsClientSpecification> smsSpecifications){
        SmsClientNameContextFactory smsClientNameContextFactory = new SmsClientNameContextFactory();
        smsClientNameContextFactory.setConfigurations(smsSpecifications);
        return smsClientNameContextFactory;
    }

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

public class AliyunSmsClientConfiguration {

    @ConditionalOnMissingBean
    @Bean
    public SmsService smsService() {
       return new AliyunSmsService();
    }
}

public class HuaWeiSmsClientConfiguration {

    @ConditionalOnMissingBean
    @Bean
    public 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
    @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)对应起来

运行查看控制台

当服务名不匹配时,再观察控制台

发现此时是走默认配置

总结

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

以上就是spring NamedContextFactory实现服务隔离的示例详解的详细内容,更多关于spring NamedContextFactory服务隔离的资料请关注脚本之家其它相关文章!

相关文章

  • 了解JAVA并发工具常用设计套路

    了解JAVA并发工具常用设计套路

    这篇文章主要介绍了了解JAVA并发工具常用设计套路,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • Java常用测试工具大全

    Java常用测试工具大全

    这篇文章主要为大家详细介绍了Java常用测试工具,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • java如何将int数组转化为Integer数组

    java如何将int数组转化为Integer数组

    这篇文章主要介绍了java如何将int数组转化为Integer数组,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java BigDecimal基础用法详解

    Java BigDecimal基础用法详解

    Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理
    2022-06-06
  • java校验json的格式是否符合要求的操作方法

    java校验json的格式是否符合要求的操作方法

    在日常开发过程中,会有这样的需求,校验某个json是否是我们想要的数据格式,这篇文章主要介绍了java校验json的格式是否符合要求,需要的朋友可以参考下
    2023-04-04
  • Spring内置任务调度如何实现添加、取消与重置详解

    Spring内置任务调度如何实现添加、取消与重置详解

    任务调度是我们日常开发中经常会碰到的,下面这篇文章主要给大家介绍了关于Spring内置任务调度如何实现添加、取消与重置的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-10-10
  • Java文件与Base64之间的转化方式

    Java文件与Base64之间的转化方式

    这篇文章介绍了如何使用Java将文件(如图片、视频)转换为Base64编码,以及如何将Base64编码转换回文件,通过提供具体的工具类实现,作者希望帮助读者更好地理解和应用这一过程
    2025-02-02
  • SpringCloudGateway开发过程解析

    SpringCloudGateway开发过程解析

    这篇文章主要介绍了SpringCloudGateway开发过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • SpringBoot整合Liquibase实现对数据库管理和迁移

    SpringBoot整合Liquibase实现对数据库管理和迁移

    Liquibase是一个用于用于跟踪、管理和应用数据库变化的开源工具,通过日志文件(changelog)的形式记录数据库的变更(changeset),然后执行日志文件中的修改,将数据库更新或回滚(rollback)到一致的状态,本文主要介绍SpringBoot与Liquibase的集成,需要的朋友可以参考下
    2024-11-11
  • 基于FileNotFoundException问题的解决

    基于FileNotFoundException问题的解决

    这篇文章主要介绍了基于FileNotFoundException问题的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03

最新评论