(四)springcloud从入门到放弃-Feign入门

Spring Cloud Feign 入门指南
本文介绍了 Spring Cloud Feign 的入门知识。Feign 是声明式 Web Service 客户端,使用简单。文中给出入门案例,包括创建 Maven 工程、利用 Feign 调用服务接口等,还阐述了原理、注解详解、开启 GZIP 压缩、日志及超时设置等内容,最后总结了其优缺点。

Springcloud Feign 入门

引言

1.在使用Spring Cloud进行微服务开发时,各个服务提供者都是以HTTP接口的形式对外提供服务
2.所以在服务消费者调用服务提供者时,底层通过HTTPClient的方式访问
3.我们可以使用JDK原生的URLConnection, Apache的HTTP Client, Netty的异步HTTP Client,Spring的RestTemplate去实现服务间的调用
4.但是最方便、最优雅的方式是通过Spring Cloud Open Feign进行服务间的调用。Spring Cloud对Feign进行了增强,使Feign支持Spring MVC的注解,并整合了Ribbon等,从而让Feign的使用更加方便

什么是 Feign

1.Feign是一个声明式的Web Service客户端,
2.使用简单,有多简单呢,只需要创建一个接口加上对应的注解,比如:@FeignClient注解
3.Feign是一种声明式、模板化的HTTP客户端。在SpringCloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的

入门案例
创建一个maven工程

在这里插入图片描述

然后以这个maven工程为父工程创建maven工程 service1-interface

在这里插入图片描述
这里需要重点讲一下

这里如果创建的是springboot工程就需要改造
1,去掉springboot项目中不需要的文件:如Application和ApplicationTests等
2,将原本的springboot打包方式改成maven打包,并install到本地库
3.原因是springboot-maven-plugin打包的第一级目录为Boot-INF,无法引用.

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source> <!--指明源码用的Jdk版本-->
                    <target>1.8</target> <!--指明打包后的Jdk版本-->
                </configuration>
            </plugin>
        </plugins>
    </build>
再以service1位父工程创建springboot子工程 service1-service

在这里插入图片描述

  • 在service1-service中创建控制器
/**
 * @Description
 * @Author Li
 * @Date 2019/5/5 14:48
 * @Version 1.0
 */
@RestController
public class PlayerController {

    @GetMapping("/service1-east")
    public List show(){
        //模拟数据 service1查到东部全明星
        List<Player> players = new ArrayList<>();
        players.add(new Player(1,33,"勒布朗","小皇帝"));
        players.add(new Player(2,23,"欧文","小科比"));
        players.add(new Player(3,28,"奥拉迪波","奥迪"));
        players.add(new Player(4,26,"科怀","机器人"));
        return players;
    }
}
  • 在service1-interface中提供实体类和提供给其他服务调用的api
/**
 * @Description 提供给其他服务调用的api
 * @Author Li
 * @Date 2019/5/514:56
 * @Version 1.0
 */
public interface EastPlayerApi {
    @GetMapping("/service1-east")
     List show();
}
service1-service的配置文件如下
spring:
  application:
    name: service1
server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka/

注册到注册中心,很好理解

启动类添加注解 开启服务发现和feign声明式服务调用
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Service1ServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(Service1ServiceApplication.class, args);
    }
}
完成后整个项目的项目目录如下

在这里插入图片描述

同理我们再创建一个service2以及service2-interface和service2-service
  • 创建完成目录如下
    在这里插入图片描述
  • 控制器如下
/**
 * @Description TODO
 * @Author apdoer
 * @Date 2019/5/5 15:11
 * @Version 1.0
 */
@RestController
public class BookController {

    @GetMapping("/showbooks")
    public List<Book> show(){
        List<Book> books = new ArrayList<>();
        books.add(new Book(1,"java高并发","apdoer"));
        books.add(new Book(2,"spring","apdoer"));
        books.add(new Book(3,"java从入门","apdoer"));
        books.add(new Book(4,"java到放弃","apdoer"));
        return books;
    }
}
  • 提供给其他服务调用的api如下
/**
 * @Description 提供给其他服务调用的api
 * @Author apdoer
 * @Date 2019/5/5 15:14
 * @Version 1.0
 */
public interface BookApi {
    @GetMapping("/showbooks")
    List<Book> show();
}
接下来我们利用feign调用service的接口
在service2-service创建包client,创建类PlayerClient,如下
/**
 * @Description 调用service2服务的PlayerController的客户端
 * @Author apdoer
 * @Date 2019/5/5 15:19
 * @Version 1.0
 */
@FeignClient("service2")
public interface PlayerClient extends EastPlayerApi {
}

@FeignClient指定调用服务的服务名.大小写忽略

在BookController中添加注入client,并调用service1中的服务
    @Autowired
    private PlayerClient client;

    @GetMapping("/service2-players")
    public List<Player> players(){
       return client.show();
    }
在service1中也添加相应的客户端调用
  • 新建client包,创建BookClient并继承service2-interface提供的BookApi
/**
 * @Description 调用服务2的BookController的客户端
 * @Author apdoer
 * @Date 2019/5/5 15:30
 * @Version 1.0
 */
@FeignClient("service2")
public interface BookClient extends BookApi{
}
  • 在PlayerController中注入BookClient,
    @Autowired
    @GetMapping("/books")
    private BookClient client;
    public List<Book> books(){
        return client.show();
    }

启动注册中心和service1和service2

可以看到访问localhost:8082/service2-players即可以调用到service1中提供的服务

在这里插入图片描述

同理访问localhost:8081/books可以调用到service2中提供的服务

在这里插入图片描述

原理

1.程序启动类添加的@EnableFeignClients注解开启对Feign Client扫描加载处理。
2.定义接口并@FeignClient注解,当程序启动时,会进行包扫描,扫描所有@FeignClients的注解的类,并将这些信息注人Spring IOC容中。
3.当定义的Feign接口中的方被调用时,通过JDK的代理 的方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创 建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,,然后把Request交给Client去处理,这里指的Client可以是JDK原生的URLConnection, Apache的Http Client,也可以是Okhttp。最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。
4.这个client是可以自定义更换的,在实际开发中也是有不少的坑,需要替换成适合你自己的client,比如apache的httpClient
5.如果接口有参数,必须使用@RequestParam("")绑定,如果是对象参数,.有更多坑,后面再说

@FeignClient注解详解

相关解释均在注释中

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    /** @deprecated */
    @Deprecated
    String serviceId() default "";

    String contextId() default "";

    @AliasFor("value")
    String name() default "";//指定FeignClient的名称.如果使用了Ribbon,name属性会作为微服务的名称,用于服务发现

    String qualifier() default "";

    String url() default ""; //url一般用于调试,可以手动指定@FeignClient调用的地址

    boolean decode404() default false;//当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException

    Class<?>[] configuration() default {};//Feign配置类,可以自定义Feign的Encoder,Decoder,LogLogLevel.Contract等

    Class<?> fallback() default void.class;//定义容错的处理类,当远程调用失败或者超时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口

    Class<?> fallbackFactory() default void.class;//工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复代码

    String path() default "";//定义当前FeignClient的统一前缀

    boolean primary() default true;
}
Feign开启GZIP压缩

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

实例配置如下
feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json #配置压缩支持的mime_types
      min-request-size: 2048  # 配置压缩数据的最小值
    response: true # 响应GZIP压缩
注意

开启GZIP压缩后,Feign之间的滴哦用通过二进制通信协议进行传输
返回值需要修改为ResponseEntity<Byte[]> 才可以正常显示,否则会导致服务间调用乱码
以下示例

@Getmapping("/show")
ResponseEntity<Byte[]>getInfo(@RequestParam("args")String args);
Feign开启日志

Feign为每个feignClient提供了一个feign.Logger实例,可以再配置中方便的开启日志

  • 在application.yml中配置日志输出
logging:
  level:
    org.apdoer.service1.service.HelloService: debug
  • 在启动类配置日志bean
@Bean
Logger.Level feignLoggerLevel(){
	return Logger.Level.Full;
}
  • 也可以通过创建@Configuration注解的类来配置
@Configuration
public class FeignConfig{
	
	@Bean
	Logger.Level feignLoggerLevel(){
		return Logger.Level.Full;
	}
}
超时设置

feign的调用分为两层,即Ribbon和Hystrix,高版本的Hystrix是默认关闭的

feign.RetryableException: Read timed out executing POST http://******
      at feign .FeignException.errorExecuting(FeignException.java:67)
      at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandl
          er.java:104)
    at feign.SynchronousMethodAandler.invoke(SynchronousMethodHandler.java:76)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.
          java:103)
      at com.sun.proxy.$Proxy113.getBaseRow(Unknown Source)
Caused by: Java.net.SocketTimeoutException: Read timed out

如果出现以上报错信息,说明ribbon处理超时,设置ribbon超时

#非请求处理的超时时间
ribbon.ReadTimeout:120000
#非请求连接的超时时间
ribbon.ConnectTimeout:30000

如果开启Hystrix.报错信息如下

com.netflix.hystrix.exception.HystrixRUntimeException:  FeignDemo#demo()timed-
      out and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
      at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
    at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(Ope
          ratorOnErrorResumeNextViaFunction.java:140)
      at  rx.internal.operators.OnSubscribeDOOnEach$DOOnEachSubscriber.
            onError(OnSubscribeDoOnEach .iava:87)

说明是Hystrix报错,配置实例

feign.hystrix.enabled: true
  # hystrix熔断机制
hystrix:
  shareSecurityContext:true
  command:
    default:
      circuitBreaker:
        sleepWindowInMilliseconds:100000
        forceClosed: true
      execution:
        isolation:
          thread:
            timeoutInMilliseconds:600000
总结

1.这样的方式是一种比较优雅的方式,需要给其他服务引用的比如pojo,比如api暴露,而具体的实现不对外暴露
2.调用过程非常简单,利用feign的继承特性,使用者感受不到是在调用其他服务的api,如同调用自己的一样
3.缺点就是服务完全由服务提供方维护,服务提供者和消费者耦合度较高,一旦服务提供需要改变,消费方也需要改变
4.关于传值有很多坑
5.本章的代码已上传到git springclou源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值