这段时间一直在用RestTemplate作restful服务的调度,与新同事交流后学到了另外的方式用Feign来调用。之前用dubbo多了,确实对spring-cloud全家桶的认识不足。今天用feign的调用方式将文件服务的相关接口作了改造。可是对@FeignClient注解的相关属性不是很清楚。同时在不指定url的状况下,feign是如何找到服务地址的?带着这两个问题,作了今天的源码解读。接下来作个总结spring
1、@FeignClient的各属性解读api
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { /** * 说明: * 一、value 与 name 互为别名,二者二选一便可 * 二、当 contextId 没有值的时候,会默认获取(value/name)的值 * 三、当未指定 url 请求地址的时候,最终会经过 ribbon-loadbalancer工具,从consul注册节点中选取 service-id 等于 value的服务做为请求地址 * @return */ @AliasFor("name") String value() default ""; /** * 说明: * 一、serviceId 已经做废,其目的与 value一致。若是设置了 serviceId,则value/name 皆以 serviceId 为准 */ /** @deprecated */ @Deprecated String serviceId() default ""; /** * 说明: * 一、当 contextId 没有值的时候,会默认获取(value/name)的值 * 二、当 qualifier 没有值的时候,会将 '${contextId}FeignClient'做为 feign 的bean组件别名 */ String contextId() default ""; /** * 说明:同 value, 二选一 */ @AliasFor("value") String name() default ""; /** * 说明: * 一、feign的bean组件别名,拥有最高优先级; * 二、当 qualifier 为空时,取 '${contextId}FeignClient' 做为 bean 的名称 */ String qualifier() default ""; /** * 说明: * 一、设置 url 之后,后续发起http调用时,直接读取该地址做为请求目标; * 二、未设置 url 时,借助 ribbon-loadbalancer 组件,根据 (value/name)从consul的服务列表中选中service-id匹配的目标服务; */ String url() default ""; boolean decode404() default false; Class<?>[] configuration() default {}; Class<?> fallback() default void.class; Class<?> fallbackFactory() default void.class; /** * 说明: * 目标服务器 对应的资源 uri (FeignClient下全部方法相同的path路径) */ String path() default ""; boolean primary() default true; }
核心源码截图以下缓存
org\springframework\cloud\spring-cloud-openfeign-core\2.1.5.RELEASE\spring-cloud-openfeign-core-2.1.5.RELEASE.jar!\org\springframework\cloud\openfeign\FeignClientsRegistrar.class服务器
2、当FeignClient未设置url时,经过ribbon查找服务的核心方法restful
3、使用Feign作restful服务调用的简单示例app
3.一、开启FeignClient的扫描(EnableFeignClients)ide
@Configuration @EnableFeignClients(basePackages = "com.simm") public class FeignConfiguration { }
3.二、设置 RequestInterceptor,统一添加受权的Header工具
@Slf4j @Component(value = "core_feign_interceptor") @AllArgsConstructor public class FeignInterceptor implements RequestInterceptor { private final ClientTokenTemplate clientTokenTemplate; /** * fengin请求添加header * @param requestTemplate */ @Override public void apply(RequestTemplate requestTemplate) { // 若是token中的类型加入了验证, 则设置rdc token Collection<String> authHeaders = requestTemplate.headers().get(TokenConstant.AUTH_TYPE); if (CollectionUtils.isNotEmpty(authHeaders) && authHeaders.contains(TokenConstant.CLIENT_AUTH)) { requestTemplate.header("Authorization", clientTokenTemplate.getRedisToken().getAccess_token()); } else { AuthInfo authInfo = BizContext.getValue(Constants.BizContextKey.AUTH); if(authInfo!=null && !StringUtils.isEmpty(authInfo.getAccess_token())){ requestTemplate.header("Authorization", authInfo.getAccess_token()); } } } }
3.三、文件服务的客户端实现测试
/** * 文件服务客户端 * * @author simm */ @ConditionalOnProperty(name = {"gateway.host", "gateway.apis.file-service"}) @FeignClient(value = "rdc-file-service", url = "${gateway.host}/${gateway.apis.file-service}", path = "/api/v2/files") public interface RdcFileClient { /** * 获取文件信息 * * @param fileId 文件ID * @return 文件内容 */ @GetMapping(value = "/{fileId}", headers = TokenConstant.GATEWAY_AUTH_HEADER) PluginFileInfoResponse getFileInfo(@PathVariable String fileId); /** * 上传文件测试 * * @param file 文件 * @return 文件注册信息 */ @PostMapping(value = "/", headers = TokenConstant.GATEWAY_AUTH_HEADER, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) PluginFileInfoResponse uploadFile(MultipartFile file); /** * 获取下载地址 * * @param fileId 文件ID * @return */ @GetMapping(value = "/{fileId}/download-url", headers = TokenConstant.GATEWAY_AUTH_HEADER) String getDownloadUrl(@PathVariable String fileId); /** * 批量获取文件详情 * * @param object 信息 * @return */ @PostMapping(value = "/batchInfos", headers = TokenConstant.GATEWAY_AUTH_HEADER, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) List<PluginFileInfoResponse> batchGetFileDetail(JSONObject object); /** * 删除缓存的文件 * * @param fileId 文件ID * @return */ @DeleteMapping(value = "/{fileId}", headers = TokenConstant.GATEWAY_AUTH_HEADER) Object delFile(@PathVariable String fileId); }