這段時間一直在用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); }