乐趣区

关于spring:聊聊如何利用服务定位器模式按需返回我们需要的服务实例

前言

什么是服务定位器模式

服务定位器是一个理解如何提供各种利用所需的服务(或组件)的对象。在服务定位器中,每个服务(或组件)都只有一个独自的实例,并通过 ID 惟一地标识。用这个 ID 就能从服务定位器中失去这个服务(或组件)。

何时能够思考应用服务定位器模式

服务定位器模式的目标是 按需返回 服务实例,当依赖是按需的或须要在运行时查找时,咱们能够应用服务定位器模式将客户端与具体实现解耦。

服务定位器蕴含的组件

  • 客户端:在运行时须要服务的消费者。
  • 服务定位器:服务定位器负责将服务按需返回给客户端。它形象了服务的查找或创立。
  • 初始上下文:它创立、注册和缓存服务。这是查找和创立的终点。
  • 服务工厂:服务工厂为服务提供生命周期治理,反对创立、查找或删除服务。
  • 服务:客户所需服务的具体实现。

服务定位器执行流程

上面咱们就以一个模仿发送短信的例子,来体验一把服务定位器模式。因 spring 曾经提供了服务定位器,本示例就以 spring 提供的服务定位器为例

前置常识

spring 服务定位器

spring 的服务定位器次要是通过 ServiceLocatorFactoryBean 实现。它实现 FactoryBean 接口,并封装了服务定位器模式的所有设计组件,为客户端提供了一个洁净的 API 以按需获取对象

spring 服务定位器实现流程

示例

1、定义一个实体类,这个实体类后边插件绑定具体短信服务会用到

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SmsRequest implements Serializable {

    private Map<String,Object> metaDatas;

    private String to;

    private String message;

    private SmsType smsType;


}

2、定义短信发送服务接口

public interface SmsProvider {SmsResponse sendSms(SmsRequest smsRequest);


}

3、定义短信服务定位器工厂,用来选取具体的短信服务

public interface SmsFactory {SmsProvider getProvider(SmsType smsType);
}

4、定义短信发送具体实现类

@Component
public class AliyunSmsProvider implements SmsProvider {
    @Override
    public SmsResponse sendSms(SmsRequest smsRequest) {System.out.println("来自阿里云短信:" + smsRequest);
        return SmsResponse.builder()
                .code("200").message("发送胜利")
                .success(true).result("阿里云短信的回执").build();}


}

注: 该具体服务必须是 spring 的 bean

5、配置 ServiceLocatorFactoryBean

 @Bean
    @ConditionalOnMissingBean
    public FactoryBean smsFactory(){ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
        serviceLocatorFactoryBean.setServiceLocatorInterface(SmsFactory.class);
        // spring beanName 映射,自定义名称映射关系,
        Properties properties = new Properties();
        properties.setProperty(SmsType.ALIYUN.toString(),"aliyunSmsProvider");
        properties.setProperty(SmsType.TENCENT.toString(),"tencentSmsProvider");
        serviceLocatorFactoryBean.setServiceMappings(properties);
        return serviceLocatorFactoryBean;
    }

注: 短信服务定位器工厂,实质是通过 beanName 来找到具体的短信服务,如下示例

public interface SmsFactory {SmsProvider getProvider(String beanName);
}

但为了放弃肯定的业务语义,咱们能够通过

   serviceLocatorFactoryBean.setServiceMappings(properties);

来实现业务类型 –>beanName 的映射

映射源码如下

    private String tryGetBeanName(@Nullable Object[] args) {
            String beanName = "";
            if (args != null && args.length == 1 && args[0] != null) {beanName = args[0].toString();}
            // Look for explicit serviceId-to-beanName mappings.
            if (serviceMappings != null) {String mappedName = serviceMappings.getProperty(beanName);
                if (mappedName != null) {beanName = mappedName;}
            }
            return beanName;
        }

6、业务如何应用

@RequiredArgsConstructor
public class SmsService {


    private final SmsFactory smsFactory;


    public SmsResponse sendSms(SmsRequest smsRequest){return smsFactory.getProvider(smsRequest.getSmsType()).sendSms(smsRequest);


    }
}

7、测试

 @Autowired
    private SmsService smsService;

    @Test
    public void testAliyunSms(){SmsRequest smsRequest = SmsRequest.builder()
                .message("模仿应用阿里云短信发送")
                .to("136000000001")
                .smsType(SmsType.ALIYUN)
                .build();

        SmsResponse smsResponse = smsService.sendSms(smsRequest);
        Assert.assertTrue(smsResponse.isSuccess());
        System.out.println(smsResponse);

    }

总结

眼尖的敌人可能会发现,你下面实现的服务定位器,用如下办法

   @Autowired
    private ApplicationContext applicationContext;


  

    @Override
    public void run(ApplicationArguments args) throws Exception {SmsProvider smsProvider = applicationContext.getBean("aliyunSmsProvider",SmsProvider.class);
        smsProvider.sendSms()}

也能实现,干嘛那么繁琐,如果你翻看源码,就会发现,他底层实现和上述的实现基本上一样

    private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception {Class<?> serviceLocatorMethodReturnType = getServiceLocatorMethodReturnType(method);
            try {String beanName = tryGetBeanName(args);
                Assert.state(beanFactory != null, "No BeanFactory available");
                if (StringUtils.hasLength(beanName)) {
                    // Service locator for a specific bean name
                    return beanFactory.getBean(beanName, serviceLocatorMethodReturnType);
                }
                else {
                    // Service locator for a bean type
                    return beanFactory.getBean(serviceLocatorMethodReturnType);
                }
            }
            catch (BeansException ex) {if (serviceLocatorExceptionConstructor != null) {throw createServiceLocatorException(serviceLocatorExceptionConstructor, ex);
                }
                throw ex;
            }
        }

其实服务定位器模式和依赖注入都是管制反转概念的实现,服务定位器将一组职责类似的服务内聚到了一起,并实现服务提供方、服务应用方齐全的解耦,下面举的例子也能够看成一种策略 + 工厂模式的具体实现。最初提一嘴,serviceLocatorFactoryBean.setServiceMappings(properties); 这个也不是必须的,只有你业务语义和 beanName 名字一样即可

demo 链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-service-locator

退出移动版