• Spring Cloud微服务笔记(五)Feign


    Feign

    一、Feign概述

    Feign是一个声明式的Web Service客户端。在Spring Cloud 中使用Feign,可以做到

    使用HTTP请求访问远程服务,就像调用本地方法一样,同时它整合了Ribbon和Hystrix。

    入门案例:

    主要依赖:

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- Spring Cloud OpenFeign的Starter的依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
        </dependencies>

    主入口程序注解:

    @SpringBootApplication
    //该注解表示当程序启动时,会进行包扫描,扫描所有带@FeignClient的注解类并进行处理
    @EnableFeignClients
    public class SpringCloudFeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringCloudFeignApplication.class, args);
        }
    }

    config:

    @Configuration
    public class HelloFeignServiceConfig {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    }

    FeignClient:

    /**
     * url = "https://api.github.com",该调用地址用于根据传入的字符串搜索github上的
     * 仓库信息。HellFeignService最终会根据指定的URL和@RequestMapping对应方法,转换成最终
     * 请求地址: https://api.github.com/search/repositories?q=spring-cloud-dubbo 
    * @FeignClient中name:指定FeignClient名称,如果项目使用了Ribbon,该名称会用户服务发现
    * configuration:Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel
    * fallback:定义容错处理类,当调用远程接口失败时,会调用接口的容错逻辑,fallback指定的类必须实现@FeignClient标记接口。
    * fallbackFactory: 用于生成fallback类示例,可实现每个接口的通用容错逻辑,减少重复代码。 *
    @author Tang Jiujia * @since 2019-03-26 */ @FeignClient(name = "github-client", url = "https://api.github.com", configuration = HelloFeignServiceConfig.class) public interface HelloFeignService { @RequestMapping(value = "/search/repositories", method = RequestMethod.GET) String searchRepo(@RequestParam("q") String queryStr); }

    controller:

    @RestController
    public class HelloFeignController {
    
        @Autowired
        HelloFeignService helloFeignService;
    
        @RequestMapping(value = "/search/github")
        public String searchGithubRepoByStr(@RequestParam("str") String queryStr) {
            return helloFeignService.searchRepo(queryStr);
        }
    }

    启动,浏览器输入:http://localhost:8012/search/github?str=spring-cloud-dubbo

    二、Feign工作原理

    主程序入口添加@EnableFeignClients注解---->定义FeignClient接口并添加@FeignClients/@FeignClient注解--->

    程序启动扫描所有拥有@FeignClients/@FeignClient注解的接口并将这些信息注入Spring IOC容器--->定义的FeignClient

    接口中的方法被调用---->通过JDK代理为每个接口方法生成RequestTemplate对象(该对象封装了HTTP请求需要的全部信息)

    ---->由RequestTemplate对象生成Request--->Request交给Client(URLConnection、Http Client等)处理--->Client被封装

    到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。

    三、Feign基础功能

    1.开启GZIP压缩

    Spring Cloud Feign支持对响应和请求进行GZIP压缩,以提高通信效率。

    通过application.yml配置:

    feign:
        compression:
            request:
                enabled: true
                mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
                min-request-size: 2048  # 配置压缩数据大小的下限
            response:
                enabled: true # 配置响应GZIP压缩

    由于开启GZIP压缩后,Feign之间的调用通过二进制协议进行传输,返回值需要修改为ResponseEntity<byte[]>,

    才可以正常显示:

    @FeignClient(name = "github-client", url = "https://api.github.com", configuration = FeignGzipConfig.class)
    public interface FeignClinet {
        @RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
        ResponseEntity<byte[]> searchRepo(@RequestParam("q") String queryStr);
    }

    2.开启日志

    application.yml:

    logging:
         level:
           cn.springcloud.book.feign.service.HelloFeignService: debug

    配置类:

    @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }

    四、Feign实战应用

    1.Feign默认Client的替换

    Feign默认使用的是JDK原生的URLConnection发送HTTP请求,没有连接池。

    1)使用HTTP Client替换默认Client

    依赖:

            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.netflix.feign</groupId>
                <artifactId>feign-httpclient</artifactId>
                <version>8.17.0</version>
            </dependency>

    application.yml:

    server:
      port: 8010
    spring:
      application:
        name: ch4-3-httpclient
    
    feign:
      httpclient:
          enabled: true

    2.使用okhttp替换Feign默认的Client

    依赖:

    <dependency>
         <groupId>io.github.openfeign</groupId>
         <artifactId>feign-okhttp</artifactId>
    </dependency>

    application.yml:

    server:
      port: 8011
    spring:
      application:
        name: okhttp
    
    
    feign:
        httpclient:
             enabled: false
        okhttp:
             enabled: true

    构建自定义的OkHttpClient:

    @Configuration
    @ConditionalOnClass(Feign.class)
    @AutoConfigureBefore(FeignAutoConfiguration.class)
    public class FeignOkHttpConfig {
        @Bean
        public okhttp3.OkHttpClient okHttpClient() {
            return new okhttp3.OkHttpClient.Builder()
                    .connectTimeout(60, TimeUnit.SECONDS)
                    .readTimeout(60, TimeUnit.SECONDS)
                    .writeTimeout(60, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true)
                    .connectionPool(new ConnectionPool())
                    .build();
        }
    }

    3.Feign的Post和Get的多参数传递

    SpringMVC是支持GET方法直接绑定POJO的,但是Feign的实现并没有覆盖所有的SpringMVC功能。

    最佳解决方式,通过Feign拦截器的方式处理:

    1)通过实现Feign的RequestInterceptor中的apply方法进行统一拦截转换处理Feign

    中的GET方法参数传递的问题。

    @Component
    public class FeignRequestInterceptor implements RequestInterceptor{
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void apply(RequestTemplate template) {
            // feign 不支持 GET 方法传 POJO, json body转query
            if (template.method().equals("GET") && template.body() != null) {
                try {
                    JsonNode jsonNode = objectMapper.readTree(template.body());
                    template.body(null);
    
                    HashMap<String, Collection<String>> queries = new HashMap<>();
                    buildQuery(jsonNode, "", queries);
                    template.queries(queries);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void buildQuery(JsonNode jsonNode, String path,
                                Map<String, Collection<String>> queries) {
            if (!jsonNode.isContainerNode()) {
                if (jsonNode.isNull()) {
                    return;
                }
                Collection<String> values = queries.get(path);
                if (null == values) {
                    values = new ArrayList<>();
                    queries.put(path, values);
                }
                values.add(jsonNode.asText());
                return;
            }
            if (jsonNode.isArray()) {   // 数组节点
                Iterator<JsonNode> it = jsonNode.elements();
                while (it.hasNext()) {
                    buildQuery(it.next(), path, queries);
                }
            } else {
                Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
                while (it.hasNext()) {
                    Map.Entry<String, JsonNode> entry = it.next();
                    if (StringUtils.hasText(path)) {
                        buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                    } else {  // 根节点
                        buildQuery(entry.getValue(), entry.getKey(), queries);
                    }
                }
            }
        }
    }

    2)集成Swagger2用于多参数传递:

    @Configuration
    @EnableSwagger2
    public class Swagger2Config {
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                    .apis(RequestHandlerSelectors
                            .basePackage("com.demon.feign"))
                    .paths(PathSelectors.any()).build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder().title("Feign多参数传递问题").description("Feign多参数传递问题")
                    .contact("Software_King@qq.com").version("1.0").build();
        }
    }

    3)消费者:

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private UserFeignService userFeignService;
    
        @RequestMapping(value = "/add", method = RequestMethod.POST)
        public String addUser(@RequestBody @ApiParam(name="用户",
                value="传入json格式",required=true) User user) {
            return userFeignService.addUser(user);
        }
    
        @RequestMapping(value = "/update", method = RequestMethod.POST)
        public String updateUser( @RequestBody @ApiParam(name="用户",value="传入json格式",required=true) User user){
            return userFeignService.updateUser(user);
        }
    }

    4)Feign Client:

    @FeignClient(name = "provider")
    public interface UserFeignService {
    
        @RequestMapping(value = "/user/add", method = RequestMethod.GET)
        public String addUser(User user);
    
        @RequestMapping(value = "/user/update", method = RequestMethod.POST)
        public String updateUser(@RequestBody User user);
    }

    5)服务提供者:

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @RequestMapping(value = "/add", method = RequestMethod.GET)
        public String addUser(User user , HttpServletRequest request){
            String token=request.getHeader("oauthToken");
            return "hello,"+user.getName();
        }
    
        @RequestMapping(value = "/update", method = RequestMethod.POST)
        public String updateUser( @RequestBody User user){
            return "hello,"+user.getName();
        }
    }
  • 相关阅读:
    The unauthenticated git protocol on port 9418 is no longer supported.
    马哥教育N63013第十五周作业
    马哥教育N63013第十四周作业
    @DateTimeFormat和@JsonFormat注解
    linux 压缩命令
    接收Mqtt数据
    我的目标
    k8s命名空间
    k8s标签
    将博客搬至CSDN
  • 原文地址:https://www.cnblogs.com/Shadowplay/p/10599755.html
Copyright © 2020-2023  润新知