链接地址:https://helpcdn.aliyun.com/document_detail/63868.html
在之前的文档中,HSF 开发已经介绍了如何使用 Spring Cloud 来开发 HSF 应用。
本文将介绍一下 HSF 的一些高级特性在 Spring Cloud 开发方式下的使用方式。目前内容包含 单元测试 和 异步调用 两部分,后续会有更多的介绍。
Demo 源码下载: sc-hsf-provider 、 sc-hsf-consumer。
单元测试
spring-cloud-starter-hsf 的实现依赖于 Pandora Boot,Pandora Boot 的单元测试可以通过 PandoraBootRunner 启动,并与 SpringJUnit4ClassRunner 无缝集成。
我们将演示一下,如何在服务提供者中进行单元测试,供大家参考。
-
在 Maven 中添加 spring-boot-starter-test 的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>
-
编写测试类的代码。
@RunWith(PandoraBootRunner.class)@DelegateTo(SpringJUnit4ClassRunner.class)// 加载测试需要的类,一定要加入 Spring Boot 的启动类,其次需要加入本类。@SpringBootTest(classes = {HSFProviderApplication.class, EchoServiceTest.class })@Componentpublic class EchoServiceTest {/*** 当使用 @HSFConsumer 时,一定要在 @SpringBootTest 类加载中,加载本类,通过本类来注入对象,否则当做泛化时,会出现类转换异常。*/@HSFConsumer(generic = true)EchoService echoService;//普通的调用@Testpublic void testInvoke() {TestCase.assertEquals("hello world", echoService.echo("hello world"));}//泛化调用@Testpublic void testGenericInvoke() {GenericService service = (GenericService) echoService;Object result = service.$invoke("echo", new String[] {"java.lang.String"}, new Object[] {"hello world"});TestCase.assertEquals("hello world", result);}//返回值 Mock@Testpublic void testMock() {EchoService mock = Mockito.mock(EchoService.class, AdditionalAnswers.delegatesTo(echoService));Mockito.when(mock.echo("")).thenReturn("beta");TestCase.assertEquals("beta", mock.echo(""));}}
异步调用
HSF 提供了两种类型的异步调用,Future 和 Callback。
-
在演示异步调用之前,我们先发布一个新的服务: com.aliware.edas.async.AsyncEchoService。
public interface AsyncEchoService {String future(String string);String callback(String string);}
-
服务提供者实现 AsyncEchoService,并通过注解发布。
@HSFProvider(serviceInterface = AsyncEchoService.class, serviceVersion = "1.0.0")public class AsyncEchoServiceImpl implements AsyncEchoService {@Overridepublic String future(String string) {return string;}@Overridepublic String callback(String string) {return string;}}
从这两点中可以看出,服务提供端与普通的发布没有任何区别,同样,之后的配置和应用启动流程也是一致的,详情请参考 HSF 开发中创建服务提供者部分的内容。
注意:异步调用的逻辑修改都在消费端,服务端无需做任何修改。
Future
-
使用 Future 类型的异步调用的消费端,也是通过注解的方式将服务消费者的实例注入到 Spring 的 Context 中,并在 @HSFConsumer 注解的 futureMethods 属性中配置异步调用的方法名。
这里我们将 com.aliware.edas.async.AsyncEchoService 的 Future 方法标记为 Future 类型的异步调用。
@Configurationpublic class HsfConfig {@HSFConsumer(serviceVersion = "1.0.0", futureMethods = "future")private AsyncEchoService asyncEchoService;}
-
方法在被标记成 Future 类型的异步调用后,同步执行时的方法返回值其实是 null,需要通过 HSFResponseFuture 来获取调用的结果。
我们在这里通过 TestAsyncController 来进行演示,示例代码如下:
@RestControllerpublic class TestAsyncController {@Autowiredprivate AsyncEchoService asyncEchoService;@RequestMapping(value = "/hsf-future/{str}", method = RequestMethod.GET)public String testFuture(@PathVariable String str) {String str1 = asyncEchoService.future(str);String str2;try {HSFFuture hsfFuture = HSFResponseFuture.getFuture();str2 = (String) hsfFuture.getResponse(3000);} catch (Throwable t) {t.printStackTrace();str2 = "future-exception";}return str1 + " " + str2;}}
调用 /hsf-future/123 ,可以看到 str1 的值为 null, str2 才是真实的调用返回值 123。
-
当服务中需要结合一批操作的返回值进行处理时,参考如下的调用方式。
@RequestMapping(value = "/hsf-future-list/{str}", method = RequestMethod.GET)public String testFutureList(@PathVariable String str) {try {int num = Integer.parseInt(str);List<String> params = new ArrayList<String>();for (int i = 1; i <= num; i++) {params.add(i + "");}List<HSFFuture> hsfFutures = new ArrayList<HSFFuture>();for (String param : params) {asyncEchoService.future(param);hsfFutures.add(HSFResponseFuture.getFuture());}ArrayList<String> results = new ArrayList<String>();for (HSFFuture hsfFuture : hsfFutures) {results.add((String) hsfFuture.getResponse(3000));}return Arrays.toString(results.toArray());} catch (Throwable t) {return "exception";}}
Callback
-
使用 Callback 类型的异步调用的消费端,首先创建一个类实现 HSFResponseCallback 接口,并通过 @Async 注解进行配置。
@AsyncOn(interfaceName = AsyncEchoService.class,methodName = "callback")public class AsyncEchoResponseListener implements HSFResponseCallback{@Overridepublic void onAppException(Throwable t) {t.printStackTrace();}@Overridepublic void onAppResponse(Object appResponse) {System.out.println(appResponse);}@Overridepublic void onHSFException(HSFException hsfEx) {hsfEx.printStackTrace();}}
AsyncEchoResponseListener 实现了 HSFResponseCallback 接口,并在 @Async 注解中分别配置 interfaceName 为 AsyncEchoService.class、methodName 为 callback。
这样,就将 com.aliware.edas.async.AsyncEchoService 的 callback 方法标记为 Callback 类型的异步调用。
-
同样,通过 TestAsyncController 来进行演示,示例代码如下:
@RequestMapping(value = "/hsf-callback/{str}", method = RequestMethod.GET)public String testCallback(@PathVariable String str) {String timestamp = System.currentTimeMillis() + "";String str1 = asyncEchoService.callback(str);return str1 + " " + timestamp;}
执行调用,可以看到如下结果:
消费端将 callback 方法配置为 Callback 类型异步调用时,同步返回结果其实是 null。
结果返回之后,HSF 会调用 AsyncEchoResponseListener 中的方法,在 onAppResponse 方法中我们可以得到调用的真实返回值。
-
如果需要将调用时的上下文信息传递给 callback ,需要使用 CallbackInvocationContext 来实现。
调用时的示例代码入下:
CallbackInvocationContext.setContext(timestamp);String str1 = asyncEchoService.callback(str);CallbackInvocationContext.setContext(null);
AsyncEchoResponseListener 示例代码如下:
@Overridepublic void onAppResponse(Object appResponse) {Object timestamp = CallbackInvocationContext.getContext();System.out.println(timestamp + " " +appResponse);}
我们可以在控制台中看到输出了
1513068791916 123,证明 AsyncEchoResponseListener 的 onAppResponse 方法通过 CallbackInvocationContext 拿到了调用前传递过来的 timestamp 的内容。