# 通过Java代码调用系统内微应用服务
本章节描述同一个系统内的微应用服务的Java代码调用规范:
- provider:服务提供端在api-module定义好feignClient和相关api接口,并在impl-module模块中实现这些接口,api-module可直接打成jar供其他consumer消费端依赖。
- consumer:服务调用端首先引入服务提供端提供的api-jar,直接使用注解@Autowired把feignClient注入当前应用中使用即可。调用端无需自己实现feignClient以及相关api接口。
# provider服务提供端
api模块pom引入相关依赖,EOS服务自带此依赖,费EOS服务需要引入
<dependency>
<groupId>com.primeton.eos</groupId>
<artifactId>eos-server-starter</artifactId>
</dependency>
启动类是否有@EnableFeignClients和@EnableHystrix注解
参数 | 说明 |
---|---|
@EnableFeignClients | SpringClouds原生注解,扫描和注册Feign客户端Bean定义 |
@EnableHystrix | SpringClouds原生注解,启动熔断降级服务 |
feign.hystrix.enabled | 是否开启hystrix,true:开启, false:不开启 |
FeignClient编写
在api模块创建feignClient接口,示例代码如下:
package com.primeton.eos.feign;
import com.primeton.eos.po.Stock;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@FeignClient(name = "STOCK-MANAGE", configuration = FeignAutoConfiguration.class, fallback = StockServerFallback.class)
public interface StockServerFeignClient {
@RequestMapping(value = "/api/stock/{versionIds}", method = GET)
Stock[] listStockByProductVersionIds(@PathVariable("versionIds") String versionIds);
@RequestMapping(value = "/api/stock/bulk-delete", method = POST)
void batchStockByVersionIds(@RequestBody Long[] versionIds);
@RequestMapping(value = "/api/stock/{versionId}", method = POST)
void deleteStockByVersionId(@PathVariable("versionId") Long versionId);
}
创建对应的fallback
@Component
public class StockServerFallback implements StockServerFeignClient {
@Override
public Stock[] listStockByProductVersionIds(String versionIds) {
//TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
return null;
}
@Override
public void batchStockByVersionIds(Long[] versionIds) {
//TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
return;
}
@Override
public void deleteStockByVersionId(Long versionId) {
//TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
return;
}
}
# 服务调用端
在服务调用端的pom中引入依赖
在pom文件中引入provider-api模块即可。
在开发中的用法
@Autowired
private StockServerFeignClient feignClient;
public void delete(String[] productIds) {
if (productIds == null) {
return;
}
mapper.deleteBatch(productIds);
versionMapper.deleteBatchByProductIds(productIds);
Map<String,Object> param = new HashMap<>();
List<Long> versionIds = new ArrayList<>();
Arrays.stream(productIds).forEach(productId -> {
param.put("productId",productId);
List<ProductVersionVO> list = versionMapper.listByProductId(param);
getVersionIds(list, versionIds);
});
//系统间不同应用调用:同步删除对应库存信息
feignClient.batchStockByVersionIds(versionIds.toArray(new Long[versionIds.size()]));
}
# 通过Java代码调用其他系统服务
本章节描述不同系统内的微应用服务的Java代码调用规范:
- provider:服务提供端无需做特殊处理,正常提供rest api即可。
- consumer:服务调用端通过restTemplate请求provider所在系统的网关,然后由网关route转发到provider。
# provider相关配置
服务提供者,发布api并授权,获取订阅码
服务提供者通过governor发布到当前系统中的网关上,并授权给服务消费者以及生成服务订阅凭证,服务发布操作如下:
服务订阅授权:
网关配置路由:
# consumer相关配置
订阅凭证和网关path配置
因为服务提供端和服务调用端分属不同的系统,运行期不能直接对服务进行访问(会被拦截,无权访问),需要通过网关路由转发服务调用请求,因此需要配置网关的PATH,供服务调用端发送服务请求,调用服务提供端发布的API。在运行期,网关PATH和订阅凭证可能会变更,因此不宜在代码中写死,需要使用Governor微服务管理平台提供的能力对应用中的网关PATH配置进行动态刷新。要使用Governor的配置能力。
配置完成后,在开发中使用@RefreseScope+@Value注解读取配置即可,如配置有变化,应用会监听配置进行热更新处理。
增加subkey配置类
ApiSubKeyConfig:用来配置apisubkey,跨系统调用api需要在header中填加apisubkey用来给网关验证调用者是否有该api调用的权限,subkey可选择存储在数据库、配置中心、配置文件等等,存储方式可自定义,用法唯一,本示例是根据subkey配置在配置文件中写的(支持Nacos热更新),如来源是其他地方,只需添加对应subkey获取方式即可。
package com.primeton.eos.config;
import com.primeton.eos.dap.sdk.api.webclient.SDKApiSubscriberProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
public class ApiSubKeyConfig implements SDKApiSubscriberProvider {
@Value("${provider.gateway.path}")
private String gatewayPath;
@Value("${product-stock.subkey}")
private String subkey;
@Override
public ApiSubscriber getSubscriber(String host, String requestPath) {
if (gatewayPath.indexOf(host) != -1) {
return new ApiSubscriber(subkey);
} else {
return new ApiSubscriber();
}
}
}
使用sdkRestTemplate进行远程调用:
@Service
@RefreshScope
public class OrderServiceImpl {
@Value("${provider.gateway.path}")
private String gatewayPath;
@Value("${product-stock.subkey}")
private String subkey;
@Autowired
@Qualifier("sdkRestTemplate") //sdkRestTemplate
@LoadBalanced
private RestTemplate sdkRestTemplate;
@Override
public OrderVO queryOrderDetail(Long orderId) {
OrderVO vo = new OrderVO();
Order order = orderRepository.findById(orderId).orElse(null);
try {
String versionIds = getVersionIds(order);
//跨系统调用
List<ProductVersionVO> products =
Arrays.asList(sdkRestTemplate.getForObject(gatewayPath + API_PATH_PRODUCT_LIST_PRODUCT_VERSION + "/{versionIds}", ProductVersionVO[].class, versionIds));
vo.setProducts(products);
} catch (Exception e) {
//记录日志
logger.error("Error queryOrderDetail in '{}', error info '{}'", orderId, e.getMessage(), e);
}
BeanUtils.copyProperties(order, vo);
return vo;
}
}