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服务端地址

示例(示例中除了client端feign代码地址

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(client端代码地址

@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再启动client、feign

10.在浏览器中输入地址查看效果

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

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

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

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

其中feign日志

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

其中lient日志

INFO findById:3
INFO findByPathId:3

这说明feign端成功调用了lient

11.将lient关闭,在浏览器中输入地址

出现报错

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>

其中feign日志

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

这说明feign进入了默认实现类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>

其中feign日志

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

这说明feign进入了默认实现类FeignServiceCauseDefault的容错逻辑

发布了92 篇原创文章 · 获赞 4 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/DingKG/article/details/103677686
今日推荐