edit-icon download-icon

Advanced HSF features

Last Updated: Jan 30, 2018

In HSF Development, we have learned how to develop HSF applications using Spring Cloud.

This topic explains some advanced features of HSF for using Spring Cloud to develop HSF applications, unit testing and asynchronous calls, and more features will be introduced later.

Download demo source code: sc-hsf-provider, sc-hsf-consumer.

Unit Testing

The implementation of spring-cloud-starter-hsf depends on Pandora Boot, and unit testing of Pandora Boot is enabled through PandoraBootRunner and seamlessly integrated with SpringJUnit4ClassRunner.

The procedure of unit testing in service providers is demonstrated as follows for your reference.

  1. Add dependency for spring-boot-starter-test in Maven.

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-test</artifactId>
    4. </dependency>
  2. Write the code of the test class.

    1. @RunWith(PandoraBootRunner.class)
    2. @DelegateTo(SpringJUnit4ClassRunner.class)
    3. // To load the test classes, you must add the startup class of Spring Boot first, and then add this class.
    4. @SpringBootTest(classes = {HSFProviderApplication.class, EchoServiceTest.class })
    5. @Component
    6. public class EchoServiceTest {
    7. /**
    8. * When using @HSFConsumer, you must add this class to the @SpringBootTest classes and use this class to inject objects, so you can avoid abnormal class conversion during generalization.
    9. */
    10. @HSFConsumer(generic = true)
    11. EchoService echoService;
    12. //Normal call
    13. @Test
    14. public void testInvoke() {
    15. TestCase.assertEquals("hello world", echoService.echo("hello world"));
    16. }
    17. //Generalized call
    18. @Test
    19. public void testGenericInvoke() {
    20. GenericService service = (GenericService) echoService;
    21. Object result = service.$invoke("echo", new String[] {"java.lang.String"}, new Object[] {"hello world"});
    22. TestCase.assertEquals("hello world", result);
    23. }
    24. //Return value Mock
    25. @Test
    26. public void testMock() {
    27. EchoService mock = Mockito.mock(EchoService.class, AdditionalAnswers.delegatesTo(echoService));
    28. Mockito.when(mock.echo("")).thenReturn("beta");
    29. TestCase.assertEquals("beta", mock.echo(""));
    30. }
    31. }

Asynchronous calls

HSF enables two types of asynchronous calling, Future and Callback.

  1. To demonstrate asynchronous calls, we have published a new service: com.aliware.edas.async.AsyncEchoService.

    1. public interface AsyncEchoService {
    2. String future(String string);
    3. String callback(String string);
    4. }
  2. The service provider implements AsyncEchoService and uses annotations to publish it.

    1. @HSFProvider(serviceInterface = AsyncEchoService.class, serviceVersion = "1.0.0")
    2. public class AsyncEchoServiceImpl implements AsyncEchoService {
    3. @Override
    4. public String future(String string) {
    5. return string;
    6. }
    7. @Override
    8. public String callback(String string) {
    9. return string;
    10. }
    11. }

    As shown above, it is the same as common service publishing. Likewise, subsequent configurations and application startup processes are all the same. Refer to “Create a service provider” in HSF Development for details.

    Note: The logic of asynchronous calls is modified on the consumer end rather than the server end.

Future

  1. To enable Future-type asynchronous calls for the consumer end, use annotations to inject instances of the service consumer into Context of Spring, and configure the method name of asynchronous calls in futureMethod properties of @HSFConsumer annotations.

    This Future method of com.aliware.edas.async.AsyncEchoService is marked as Future-type asynchronous calls.

    1. @Configuration
    2. public class HsfConfig {
    3. @HSFConsumer(serviceVersion = "1.0.0", futureMethods = "future")
    4. private AsyncEchoService asyncEchoService;
    5. }
  2. After the method is marked as Future-type asynchronous calls, the actual return value of the method during synchronous execution is null, and the calling result must be obtained through HSFResponseFuture.

    TestAsyncController is used for demonstration. The sample code is as follows:

    1. @RestController
    2. public class TestAsyncController {
    3. @Autowired
    4. private AsyncEchoService asyncEchoService;
    5. @RequestMapping(value = "/hsf-future/{str}", method = RequestMethod.GET)
    6. public String testFuture(@PathVariable String str) {
    7. String str1 = asyncEchoService.future(str);
    8. String str2;
    9. try {
    10. HSFFuture hsfFuture = HSFResponseFuture.getFuture();
    11. str2 = (String) hsfFuture.getResponse(3000);
    12. } catch (Throwable t) {
    13. t.printStackTrace();
    14. str2 = "future-exception";
    15. }
    16. return str1 + " " + str2;
    17. }
    18. }

    Call /hsf-future/123; the str1 value is null, and str2 value is 123, which is the actual return value.

    调用 /hsf-future/123 单结果

  3. If a series of operation return values are needed for service processing, refer to the following call method.

    1. @RequestMapping(value = "/hsf-future-list/{str}", method = RequestMethod.GET)
    2. public String testFutureList(@PathVariable String str) {
    3. try {
    4. int num = Integer.parseInt(str);
    5. List<String> params = new ArrayList<String>();
    6. for (int i = 1; i <= num; i++) {
    7. params.add(i + "");
    8. }
    9. List<HSFFuture> hsfFutures = new ArrayList<HSFFuture>();
    10. for (String param : params) {
    11. asyncEchoService.future(param);
    12. hsfFutures.add(HSFResponseFuture.getFuture());
    13. }
    14. ArrayList<String> results = new ArrayList<String>();
    15. for (HSFFuture hsfFuture : hsfFutures) {
    16. results.add((String) hsfFuture.getResponse(3000));
    17. }
    18. return Arrays.toString(results.toArray());
    19. } catch (Throwable t) {
    20. return "exception";
    21. }
    22. }

    调用 /hsf-future/123 多结果

Callback

  1. To enable Callback-type asynchronous calls for the consumer end, create a class to implement the HSFResponseCallback interface and use @Async annotations for configuration.

    1. @AsyncOn(interfaceName = AsyncEchoService.class,methodName = "callback")
    2. public class AsyncEchoResponseListener implements HSFResponseCallback{
    3. @Override
    4. public void onAppException(Throwable t) {
    5. t.printStackTrace();
    6. }
    7. @Override
    8. public void onAppResponse(Object appResponse) {
    9. System.out.println(appResponse);
    10. }
    11. @Override
    12. public void onHSFException(HSFException hsfEx) {
    13. hsfEx.printStackTrace();
    14. }
    15. }

    AsyncEchoResponseListener implements the HSFResponseCallback interface, and configures interfaceName to AsyncEchoService.class, and methodName to callback in @Async annotations.

    The Callback method of com.aliware.edas.async.AsyncEchoService is marked as Callback-type asynchronous calls.

  2. Similarly, TestAsyncController is used for demonstration. The sample code is as follows:

    1. @RequestMapping(value = "/hsf-callback/{str}", method = RequestMethod.GET)
    2. public String testCallback(@PathVariable String str) {
    3. String timestamp = System.currentTimeMillis() + "";
    4. String str1 = asyncEchoService.callback(str);
    5. return str1 + " " + timestamp;
    6. }

    Execute the call, and you can see the following results:

    调用 /hsf-callback/123 结果

    After the consumer end configures the callback method to Callback-type asynchronous calling, the synchronous return value is actually null.

    After the result is returned, HSF calls the method in AsyncEchoResponseListener, and the actual return value of calling can be obtained using the onAppResponse method.

  3. Use CallbackInvocationContext to transmit the contextual information of the calling to Callback.

    The sample code for calling is as follows:

    1. CallbackInvocationContext.setContext(timestamp);
    2. String str1 = asyncEchoService.callback(str);
    3. CallbackInvocationContext.setContext(null);

    The sample code of AsyncEchoResponseListener is as follows:

    1. @Override
    2. public void onAppResponse(Object appResponse) {
    3. Object timestamp = CallbackInvocationContext.getContext();
    4. System.out.println(timestamp + " " +appResponse);
    5. }

    We can see the output in the console is 1513068791916 123, showing that the onAppResponse method of AsyncEchoResponseListener has used CallbackInvocationContext to receive timestamp contents transmitted before the call.

    hsf callback console

Thank you! We've received your feedback.