Spring cloud HSF 整合 HSF 高階特性
在之前的文件中,HSF 開發已經介紹瞭如何使用 Spring Cloud 來開發 HSF 應用。
本文將介紹一下 HSF 的一些高階特性在 Spring Cloud 開發方式下的使用方式。目前內容包含 單元測試 和 非同步呼叫 兩部分,後續會有更多的介紹。
單元測試
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 })
@Component
public class EchoServiceTest {
/**
* 當使用 @HSFConsumer 時,一定要在 @SpringBootTest 類載入中,載入本類,通過本類來注入物件,否則當做泛化時,會出現類轉換異常。
*/
@HSFConsumer(generic = true)
EchoService echoService;
//普通的呼叫
@Test
public void testInvoke() {
TestCase.assertEquals("hello world", echoService.echo("hello world"));
}
//泛化呼叫
@Test
public 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
@Test
public 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 {
@Override
public String future(String string) {
return string;
}
@Override
public String callback(String string) {
return string;
}
}
從這兩點中可以看出,服務提供端與普通的釋出沒有任何區別,同樣,之後的配置和應用啟動流程也是一致的,詳情請參考 HSF 開發中建立服務提供者部分的內容。
注意:非同步呼叫的邏輯修改都在消費端,服務端無需做任何修改。
Future
-
使用 Future 型別的非同步呼叫的消費端,也是通過註解的方式將服務消費者的例項注入到 Spring 的 Context 中,並在 @HSFConsumer 註解的 futureMethods 屬性中配置非同步呼叫的方法名。
這裡我們將 com.aliware.edas.async.AsyncEchoService 的 Future 方法標記為 Future 型別的非同步呼叫。
@Configuration
public class HsfConfig {
@HSFConsumer(serviceVersion = "1.0.0", futureMethods = "future")
private AsyncEchoService asyncEchoService;
}
-
方法在被標記成 Future 型別的非同步呼叫後,同步執行時的方法返回值其實是 null,需要通過 HSFResponseFuture 來獲取呼叫的結果。
我們在這裡通過 TestAsyncController 來進行演示,示例程式碼如下:
@RestController
public class TestAsyncController {
@Autowired
private 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{
@Override
public void onAppException(Throwable t) {
t.printStackTrace();
}
@Override
public void onAppResponse(Object appResponse) {
System.out.println(appResponse);
}
@Override
public 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 示例程式碼如下:
@Override
public void onAppResponse(Object appResponse) {
Object timestamp = CallbackInvocationContext.getContext();
System.out.println(timestamp + " " +appResponse);
}
我們可以在控制檯中看到輸出了
1513068791916 123
,證明 AsyncEchoResponseListener 的 onAppResponse 方法通過 CallbackInvocationContext 拿到了呼叫前傳遞過來的 timestamp 的內容。