SpringCloud之Feign的簡單實例,包括聲明式REST調用及容錯處理(springboot2.2.2RELEASE)

feign說明

服務之間都是以 HTTP 接口的形式對外提供服務的。消費者在進行調用的時候,底層是通過 HTTP Client 進行訪問。也可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 異步 Http Client,Spring 的 RestTemplate 去實現服務間的調用。但是最方便、最優雅的方式是通過 Spring Cloud Open Feign 進行服務間的調用 Spring Cloud 對 Feign 進行了增強,使 Feign 支持 Spring Mvc 的註解,並整合了 Ribbon 等,從而讓 Feign 的使用更加方便。

Feign 特性:

  • 可插拔的註解支持,包括 Feign 註解和AX-RS註解。
  • 支持可插拔的 HTTP 編碼器和解碼器。
  • 支持 Hystrix 和它的 Fallback。
  • 支持 Ribbon 的負載均衡。
  • 支持 HTTP 請求和響應的壓縮。

Feign 是一個聲明式的 WebService 客戶端,它的目的就是讓 Web Service 調用更加簡單。它整合了 Ribbon 和 Hystrix,從而不需要開發者針對 Feign 對其進行整合。Feign 還提供了 HTTP 請求的模板,通過編寫簡單的接口和註解,就可以定義好 HTTP 請求的參數、格式、地址等信息。Feign 會完全代理 HTTP 的請求,在使用過程中我們只需要依賴注入 Bean,然後調用對應的方法傳遞參數即可。

Feign 工作原理

  • 在開發微服務應用時,我們會在主程序入口添加 @EnableFeignClients 註解開啓對 Feign Client 掃描加載處理。根據 Feign Client 的開發規範,定義接口並加 @FeignClient 註解。

  • 當程序啓動時,會進行包掃描,掃描所有 @FeignClient 的註解的類,並將這些信息注入 Spring IOC 容器中。當定義的 Feign 接口中的方法被調用時,通過JDK的代理的方式,來生成具體的 RequestTemplate。當生成代理時,Feign 會爲每個接口方法創建一個 RequetTemplate 對象,該對象封裝了 HTTP 請求需要的全部信息,如請求參數名、請求方法等信息都是在這個過程中確定的。

  • 然後由 RequestTemplate 生成 Request,然後把 Request 交給 Client 去處理,這裏指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最後 Client 被封裝到 LoadBalanceclient 類,這個類結合 Ribbon 負載均衡發起服務之間的調用。

@FeignClient 註解屬性

  • name:指定 Feign Client 的名稱,如果項目使用了 Ribbon,name 屬性會作爲微服務的名稱,用於服務發現。

  • url:url 一般用於調試,可以手動指定 @FeignClient 調用的地址。

  • decode404:當發生404錯誤時,如果該字段爲 true,會調用 decoder 進行解碼,否則拋出 FeignException。

  • configuration:Feign 配置類,可以自定義 Feign 的 Encoder、Decoder、LogLevel、Contract。

  • fallback:定義容錯的處理類,當調用遠程接口失敗或超時時,會調用對應接口的容錯邏輯,fallback 指定的類必須實現 @FeignClient 標記的接口。

  • fallbackFactory:工廠類,用於生成 fallback 類示例,通過這個屬性我們可以實現每個接口通用的容錯邏輯,減少重複的代碼。

  • path:定義當前 FeignClient 的統一前綴。

以上內容來自:Feign 基本使用

示例

示例中eureka註冊中心代碼地址

示例中(客戶端)代碼地址

1.pomxml

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.2.3</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>


    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

2.在啓動類上加@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class CloudFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudFeignApplication.class, args);
    }

}

3.配置文件

feign.hystrix.enabled爲true纔可以使用容錯

spring.application.name=cloud-feign

server.servlet.context-path=/feign

server.port=8035
# logging 配置
logging.config=classpath:log4j2.xml

#ribbon 飢餓加載
ribbon.eager-load.enabled=true
#指定需要飢餓加載的客戶端名稱、服務名
ribbon.eager-load.clients=cloud-client,cloud-feign
ribbon.ConnectTimeout=90000
ribbon.ReadTimeout=90000

#根據ip註冊實例
eureka.instance.prefer-ip-address=true

#指定註冊實例ID(默認是主機名:應用名:應用端口)
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#指定註冊實例主機名
#eureka.instance.hostname=127.0.0.1
#eureka.instance.hostname= ${spring.cloud.client.ip-address}

#註冊地址 eureka服務端的地址 多節點用,分隔
eureka.client.service-url.defaultZone=http://127.0.0.1:8025/eureka/
#打開hystrix
feign.hystrix.enabled=true

4.controller

    private FeignService feignService;

    @GetMapping("/{id}")
    public Object findByPathId(@PathVariable Long id) {
        log.info("Path-->id:" + id);
        return feignService.findByPathId(id);
    }
    @GetMapping("findById")
    public Object findById(@RequestParam(name = "id") Long id) {
        log.info("findById-->id:" + id);
        return feignService.findById(id);
    }

5.service

@FeignClient(name = "cloud-client/client/api")註解不捕獲異常,也沒有默認實現

@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)註解不捕獲異常,但是有默認實現,默認實現類是FeignServiceImplDefault

@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)註解即捕獲異常,也有默認實現,還可以捕獲異常信息。對應的類是FeignServiceCauseDefault

//不捕獲異常,沒有默認實現
@FeignClient(name = "cloud-client/client/api")
//不捕獲異常,只有默認實現
//@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
//捕獲異常和默認實現
//@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
public interface FeignService {
    @GetMapping("/{id}")
    Object findByPathId(@PathVariable(name = "id") Long id);

    @GetMapping("findById")
    Object findById(@RequestParam(name = "id") Long id);
}

6.FeignServiceCauseDefault

@Service
@Log4j2
public class FeignServiceCauseDefault implements FallbackFactory<FeignService> {
    @Override
    public FeignService create(Throwable throwable) {
        return new FeignService() {
            @Override
            public Object findById(Long id) {
                log.info("sorry, fallback. reason was: " + throwable);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", id);
                log.info("FeignServiceCauseDefault-->findById-->id::" + id);
                return jsonObject;
            }

            @Override
            public Object findByPathId(Long id) {
                log.info("sorry, fallback. reason was: " + throwable);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", id);
                log.info("FeignServiceCauseDefault-->findByPathId-->id:" + id);
                return jsonObject;
            }
        };
    }


}

7.FeignServiceImplDefault

@Service
@Log4j2
public class FeignServiceImplDefault implements FeignService {

    @Override
    public Object findById(Long id) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id);
        log.info("FeignServiceImplDefault-->findById-->id:" + id);
        return jsonObject;
    }

    @Override
    public Object findByPathId(Long id) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id);
        log.info("FeignServiceImplDefault-->findByPathId-->id:" + id);
        return jsonObject;
    }
}

8.被調用者的Controller(服務端代碼地址

@RestController
@RequestMapping("api")
@Log4j2
public class ClientController {

    @GetMapping("findById")
    public Object findById(@RequestParam(name = "id") Long id) {
        log.info("findById:"+id);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id);
        return jsonObject;
    }

    @GetMapping("findById2")
    public Object findById2(@RequestParam(name = "id") Long id) {
        log.info("findById2:"+id);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id+2);
        return jsonObject;
    }

    @GetMapping("/{id}")
    public Object findByPathId(@PathVariable Long id) {
        log.info("findByPathId:"+id);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("findByPathId", id);
        return jsonObject;
    }
}

9.先啓動eureka註冊中心再啓動服務端、客戶端

10.在瀏覽器中輸入地址查看效果

輸入http://localhost:8035/feign/findById?id=3

<LinkedHashMap>
<id>3</id>
</LinkedHashMap>

輸入http://localhost:8035/feign/3

<LinkedHashMap>
<findByPathId>3</findByPathId>
</LinkedHashMap>

其中客戶端日誌

INFO findById-->id:3
INFO Path-->id:3

其中服務端日誌

INFO findById:3
INFO findByPathId:3

這說明客戶端成功調用了服務端

11.將服務端關閉,在瀏覽器中輸入地址

出現報錯

12.只打開@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)註解,並在瀏覽器中輸入地址

輸入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3

<JSONObject>
<id>3</id>
</JSONObject>

其中客戶端日誌

INFO findById-->id:3
INFO FeignServiceImplDefault-->findById-->id:3

這說明客戶端執行了默認實現類FeignServiceImplDefault的容錯邏輯

13.只打開@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)註解,並在瀏覽器中輸入地址

輸入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3

<JSONObject>
<id>3</id>
</JSONObject>

其中客戶端日誌

INFO findById-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findById-->id::3

或者是

INFO Path-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findByPathId-->id:3

這說明客戶端執行了默認實現類FeignServiceCauseDefault的容錯邏輯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章