本文描述了使用 Spring Boot 的编程模型在 Pandora Boot 应用中使用 HSF
maven依赖
导入pandora-boot-starter-bom
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<!-- http://gitlab.alibaba-inc.com/middleware-container/pandora-boot/wikis/spring-boot-2 -->
<version>2.5.12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.taobao.pandora</groupId>
<artifactId>pandora-boot-starter-bom</artifactId>
<version>2023-07-stable-hsf</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在<dependencies>
(注意不是<dependencyManagement>/<dependencies>
)下添加依赖(无须写版本):
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
</dependency>
全局配置
在 application.properties
添加所有 HSF的默认版本信息和客户端超时信息, 样例配置如下:
spring.hsf.group=HSF
spring.hsf.version=1.0.0
spring.hsf.timeout=3000
@HSFProvider
注意是
com.alibaba.boot.hsf.annotation.HSFProvider
,不是com.taobao.hsf.app.spring.util.annotation.HSFProvider
首先要编写服务提供者
只需要在发布的服务上添加@HSFProvider,其中DemoService是要发布的服务的接口,注意:注解是加在实现类的头上。
@HSFProvider(serviceInterface = DemoService.class)
public class DemoServiceImpl implements DemoService{}
还可以通过该注解为特定的服务设置配置,可以覆盖全局配置。
@HSFProvider(serviceInterface = DemoService.class, serviceGroup = "HSF", serviceVersion = "1.0.1", clientTimeout = 500)
public class DemoServiceImpl implements DemoService{}
@HSFProvider
中的 serviceGroup
和 serviceVersion
以及其他 String 类型的配置都可以支持 Spring 的 property placeholder, 如:
@HSFProvider(serviceInterface = DemoService.class, serviceGroup = "${service.group.name}")
public class DemoServiceImpl implements DemoService{}
注意:springboot默认只扫描main函数所在的package,如果服务在其他的package里,需要配置@componentScan
@ComponentScan(basePackages = "com.taobao.mypackage")
此前总结:和feign调用类似,但比feign更简洁,此处只需要在实现类上加注解,但是feign需要专门写feignclient,而且feign是针对controller写的,但是hsf是直接对实现类加注解。
假如我要调用其他的服务中的服务,我需要在目前的项目的启动类上加上@componentScan注解,这样才可以将对应服务的对象加载到我的ioc容器中,才可以通过di注入。
接下来就是消费服务了
方法一:直接使用@HSFConsumer注入需要的接口(注意:此处注入的是接口,但是消费提供的时候加注解的地方是实现类上,不要混淆)
@Component
public class DemoClient {
@HSFConsumer(serviceGroup = "HSF", serviceVersion = "1.0.0")
private DemoService demoService;
}
如果在配置文件中定义了全局默认配置,那么在serviceGroup和serviceVersion 是可以不需要指定的,如果想指定特定值,可以通过注解属性进行覆盖。当然和服务提供者一样,此处一样可以用占位符
注意:如果在使用全局配置时,同时需要显式配置
serviceVersion
为1.0.0.DAILY
,可以使用spring placeholder ${}的方式来处理。
除了这种方式还可以通过配置类配置一次,然后多处直接注入
@Configuration
public class HsfConfig {
@HSFConsumer(serviceVersion = "2.0.1", serviceGroup = "product", clientTimeout = 10000)
HelloWorldService helloWorldService;
}
在使用时直接注入即可:
@Autowired
HelloWorldService helloWorldService;
方法二:创建一个Java Configuration,然后可以通过代码方式引入Hsf
@Configuration
public class DemoAutoConfigure {
@Bean
public DemoService demoService() {
return (DemoService) ServiceFactory.consumer()
.service(DemoService.class)
.group("HSF")
.version("1.0.0")
.sync()
.subscribe();
}
}
注入服务
@Autowired
private DemoService demoService;
第三种方式:使用传统的xml配置文件装配Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloWorldConsumer" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean" init-method="init">
<property name="interfaceName" value="com.acme.DemoService"/>
<property name="version" value="1.0.0"/>
<property name="group" value="HSF"/>
</bean>
</beans>
然后在对应的启动类上添加@ImportResource导入自定义的xml配置文件
以@Bean
方式创建 HSFSpringProviderBean 和 HSFSpringConsumerBean
如果使用以上注解出现变量无法注入的问题,可以使用@Bean的配置方式
操作如下:
接口
现有一个DemoService的接口:
public interface DemoService {
public String hello(String msg);
}
接口的实现
接口的实现是一个Spring bean:
@Service
public class DemoServiceImpl implements DemoService{
@Override
public String hello(String msg) {
return "hello " + msg;
}
}
配置 HSFSpringProviderBean
@Configuration
public class HsfProviderConfig {
@Bean(initMethod = "init")
public HSFSpringProviderBean demoserviceProvider(DemoServiceImpl demoServiceImpl) {
HSFSpringProviderBean provider = new HSFSpringProviderBean();
provider.setServiceInterfaceClass(DemoService.class);
provider.setTarget(demoServiceImpl);
provider.setServiceVersion("1.0.0");
provider.setServiceGroup("HSF");
return provider;
}
}
配置 HSFSpringConsumerBean
@Configuration
public class HsfConsumerConfig {
@Bean(initMethod = "init")
public HSFSpringConsumerBean demoserviceConsumer() {
HSFSpringConsumerBean consumer = new HSFSpringConsumerBean();
consumer.setInterfaceClass(DemoService.class);
consumer.setVersion("1.0.0");
consumer.setGroup("HSF");
return consumer;
}
}
当要使用这个consumer时,直接注入就可以了:
@Autowired
DemoService demoserviceConsumer;
调试支持: 直连某一HSF Server
本功能需要应用显式设置vm arguments为-Dhsf.run.mode=0
, 参考HSF文档。
在开发阶段, 如果你需要调试, 你需要连接到某一主机上, 这个时候你需要设置一下某一接口调用的目标地址. HSF Service的目标地址可以在 hsfops 服务查询 里查到
spring.hsf.routes.com.acme.DemoService=1.2.3.4
或者通过 HSF LightAPI 支持 中调用 target(ip)
方法来指定。
通过这种方式,你就可以快速将HSF服务调用路由到指定的主机上, 方便你进行调试. 请不要在生产环境使用
单元测试
Pandora Boot 应用的单元测试可以通过 PandoraBootRunner
启动,并与 SpringJUnit4ClassRunner
无缝集成
@RunWith(PandoraBootRunner.class)
@DelegateTo(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
@Component
public class ApplicationTest {
@HSFConsumer(generic = true)
DemoService consumer;
@Test
public void testInvoke() {
TestCase.assertEquals("hello : pandora boot", consumer.sayHello("pandora boot"));
}
@Test
public void testGenericInvoke() {
GenericService service = (GenericService) consumer;
Object result = service.$invoke("sayHello", new String[]{String.class.getName()}, new Object[]{"pandora boot"});
TestCase.assertEquals("hello : pandora boot", result);
}
}
在单元测试中,往往需要 mock 掉服务调用的方法,下面给出了使用 Mockito 的范例:
@Test
public void testMock() {
DemoService mock = Mockito.mock(DemoService.class, AdditionalAnswers.delegatesTo(consumer));
Mockito.when(mock.sayHello("ping")).thenReturn("pong");
TestCase.assertEquals("pong", mock.sayHello("ping"));
TestCase.assertEquals("hello : pandora boot", mock.sayHello("pandora boot"));
}
HSF LightAPI 支持
在 如何消费HSF服务(方法二) 中初步展示了 HSF lightapi 的用法。HSF lightapi 提供了一种方便的编程方式给开发人员提供 HSF 服务或者消费 HSF 服务。
服务消费方
DemoService demoService = (DemoService) ServiceFactory.consumer()
.service(DemoService.class)
.group("HSF")
.version("1.0.0")
.generic()
.sync()
.subscribe();
服务提供方
ServiceFactory.provider()
.service(com.acme.DemoService)
.name("demo")
.impl(new com.acme.DemoServiceImpl())
.group("HSF")
.version("1.0.0")
.timeout(3000)
.publish();
HSF不仅支持同步调用,而且还支持异步调用,共有两种方式,第一种是在调用完毕后在需要获取结果的地方,执行以下代码可获得执行结果。
调用执行后,可以通过以下的代码获取到执行结果。更详细的用法请参阅 HSF Future 调用文档
HSFFuture hsfFuture = HSFResponseFuture.getFuture();
...
Object result = future.getResponse(5000);
第二种方式,远程调用结束后自动回调调用方的回调函数 代码如下:
通过实现 HSFResponseCallback
以及在实现类上标注 @AsyncOn
可以很方便的支持 HSF Consumer 的 callback listener。更详细的用法请参阅 HSF 异步回调文档
@AsyncOn(interfaceName = Echo.class, methodName = "ping")
public class EchoServiceResponseListener implements HSFResponseCallback {
@Override
public void onAppException(Throwable t) {}
@Override
public void onAppResponse(Object appResponse) {}
@Override
public void onHSFException(HSFException hsfEx) {}
}
HSF的一些配置参数说明
HSF会从System Properties中读取相关的一些配置项, 相关的配置项说明在 http://gitlab.alibaba-inc.com/middleware/hsf2-0/wikis/vnarguments
一些特别的说明:
- project.name: 从 spring.application.name 以及 project.name 中获取应用名
配置的覆盖关系
对于 @HSFProvider
, @HSFConsumer
这两个 annotation 上定义的值与 application.properties 中定义的值,是存在覆盖关系的: annotation 上的默认值 < application.properties 上全局的值 (比如 spring.hsf.version) < annotation 上指定的值 < application.properties 上为单独服务指定的值。简单的说,覆盖关系可以用下面两个原则来总结:
- 外面的配置覆盖里面的配置:-D参数 > application.properties > Annotation
- 显式指定的配置优先默认配置
进一步的,在 application.properties
中可以为各个 interface 配置 group, version, 或者 timeout:
spring.hsf.groups.com.acme.DemoService = HSF
spring.hsf.versions.com.acme.DemoService = 1.0.0
spring.hsf.timeouts.com.acme.DemoService = 2000
注意在这种情况下前缀是复数
spring.hsf.groups
,spring.hsf.versions
,spring.hsf.timeouts
通过复数形式对特定服务单独指定配置的还有:
- spring.hsf.configserver-centers: 服务多注册中心发布
- spring.hsf.delay-publishes: 优雅上下线
- spring.hsf.enables: 暂时禁用特定服务。比如
spring.hsf.enables.com.test.pandora.hsf.HelloWorldService=false
补充说明:非 Spring Boot 场景
在非 Spring Boot 场景中同样可以使用 @HSFProvider
以及 @HSFConsumer
,示例如下:
public class Application {
public static void main(String[] args) {
PandoraBootstrap.run(args);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(HsfProviderAutoConfiguration.class);
context.scan(DemoService.class.getPackage().getName());
context.refresh();
PandoraBootstrap.markStartupAndWait();
}
}
@HSFProvider(serviceInterface = DemoService.class)
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "hello : " + name;
}
}
需要注意的是,在这种使用场景下,需要在 pom.xml 中显示的排除掉 spring-boot-autoconfigure
的依赖
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>pandora-hsf-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
集成 Hystrix
Endpoint
当启用了spring boot endpoint时,可以访问: http://localhost:7002/hsf
参考文档
- HSF WIKI: http://gitlab.alibaba-inc.com/middleware/hsf2-0/wikis/home
- HSF JVM参数配置: http://gitlab.alibaba-inc.com/middleware/hsf2-0/wikis/vnarguments
- HSF 控制台: http://ops.jm.taobao.net/hsfops/serviceSearch.htm?envType=daily
- Pandora Boot 中调用 dubbo 服务: https://www.atatech.org/articles/89969